대외활동/SK Shieldus rookies

[인프라 활용을 위한 파이썬] #1 파일&디렉토리, 메일 자동화

승요나라 2024. 3. 7. 10:55

루키즈 교육의 막을 열었던 강의 (with 조정원 강사님)

교재는 아래와 같다.

https://product.kyobobook.co.kr/detail/S000001033121

 

뚝딱뚝딱 파이썬 자동화 | 알 스웨이가트 - 교보문고

뚝딱뚝딱 파이썬 자동화 | 이제 단순 반복 작업은 파이썬으로 해치운다 컴퓨터로 하는 작업 중에서 단순 반복 작업이 의외로 적지 않다. 파일 이름을 일일이 바꾼다거나 스프레드시트 셀을 수천

product.kyobobook.co.kr

굉장히 딱딱하게 적혀있어 추천하진 않음

 


 

IT 보안에서의 파이썬

  • 모의해킹 오픈 도구(=오픈소스로 만들어진 도구)의 80% 이상이 파이썬이다.
  • 자동화 업무에 파이썬이 많이 사용된다.
  • 모든 운영체제에서 호환성이 좋다.

 

Tool - Visual Studio Code

권한 문제로 인해 C:보다 D:에 소스 코드를 두는 것을 권장함

 

아래는 실습한 파이썬 코드이다.

print("파이썬 웰컴")
print("파이썬", "웰컴", "python")
print("파이썬 \n", "웰컴\n")
print('파이썬 "매우" 웰컴')
print(""" 굉장히 긴 문자열입니다.........
      파이썬 화이팅
      파이썬자동화
      """)

test.py

 

a = 17
b = "화이팅"

print(type(a))
print(type(b))

# 일반적인 출력
print("파이썬 " + str(a) + "기 " + b)

# format 방식 출력
print("파이썬 {}기 SK쉴더스 {}".format(a,b))

# f-string 방식 출력
print(f"파이썬 {a}기 SK쉴더스 {b}")

input01.py

 

name = input("이름을 입력하세요 : ")
phone = input("전화번호를 입력하세요 : ")
age = int(input("나이를 입력하세요 : "))

#일반적인 출력
print(name + "의 전화번호는", phone, "입니다", age, "살 입니다.")

#format 메서드
print("{}의 전화번호는 {}입니다. 나이는 {} 살입니다.".format(name,phone,age))

#f-string
print(f"{name}의 전화번호는 {phone} 입니다. 나이는 {age} 살입니다")

input02.py

파이썬의 세 가지 출력 방법

  • 일반적인 출력 : 가독성이 떨어질 수 있고, 코드를 작성하기 번거로움
  • format 메서드
  • f-string : Python 3.6부터 도입된 새로운 문자열 포맷팅 방식으로, 가독성이 좋고 가장 간결하며 직관적임

기본적으로 Python 3.6 이상 버전에서는 f-string을 사용하는 것이 권장된다고 한다.

 

import googletrans

translator = googletrans.Translator()

input_text = input("한글을 입력하세요")
translated = translator.translate(input_text, dest='en').text

print(f" 한글 입력 값: {input_text}")
print(f" 영어로 번역한 값: {translated}")

trans01.py

googletrans 라이브러리를 이용한 한글 영어 번역 프로그램

(pip install googletrans=3.1.0a0 명령어를 통해 3.1.0a0 버전 패키지 설치 필요)

 

names = ["조정원", "홍길동", "김철", "정군", "박군"]

#사용자를 추가
name_input = input("추가할 이름은: ")
names.append(name_input)

#사용자 리스트를 출력
for i in range(len(names)):
    print(f"인덱스: {i+1}의 이름은: {names[i]}")

list01.py

 

scores = [10.0, 9.0, 9.5, 7.1, 5, 8.0]


print(f"제거전 {scores}")
scores.remove(max(scores))
scores.remove(min(scores))
print(f"제거후 {scores}")

print(f"평균값: {sum(scores)/len(scores)}")

list02.py

 

# 성적 입력 프로그램
# 학생 5명의 성적을 입력하여 성적 평균, 최고점수, 최소점수, 평균값을 구한다.
# 80점 이상 학생 수는 count 한다.

STUDENTS = 5
scores = [] # 리스트 초기화
count = 0

for i in range(STUDENTS):
    value = int(input(f"{i+1} 번째 성적을 입력하세요."))
    scores.append(value)
    if value >= 80:
        count = count + 1

print(f"합 {sum(scores)}")
print(f"최소점수 {min(scores)}")
print(f"최고점수 {max(scores)}")
print(f"평균값 {sum(scores)/len(scores)}")
print(f"80점 점수 이상 = {count}명")

list03.py

 

menus = {
    "아메리카노": 4000,
    "카페라떼": 5000,
    "카푸치노": 5000,
    "바닐라라떼": 5500,
}

for name in menus.keys():
    print(name)

for price in menus.values():
    print(price)

selected_menu = input("주문할 메뉴를 입력하세요.")

if selected_menu in menus:
    price = menus[selected_menu]
    print(f"{price}")

dic_test.py

 

# 커피 주문 프로그램

# 각 메뉴에 대한 가격 정보를 담은 딕셔너리
menus = {
    "아메리카노": 4000,
    "카페라떼": 5000,
    "카푸치노": 5000,
    "바닐라라떼": 5500,
}

# 메뉴 리스트 출력
# .items()를 사용하면 key와 value 값을 자동으로 가져온다.
print(f"==== 메뉴 리스트 ====")
for name, price in menus.items():
    print(f"{name}: {price}원")

# 주문한 메뉴들을 담을 리스트
order_list = []

# 주문 반복문
while True:
    selected_menu = input("주문할 메뉴를 입력하세요. (종료: 'q')")
    if selected_menu == 'q':  # 사용자가 'q'를 입력하면 주문 종료
        break
    else:
        order_list.append(selected_menu)  # 주문한 메뉴를 리스트에 추가

# 총 주문 금액 계산
total_price = sum(menus[menu_name] for menu_name in order_list)

# 사용자로부터 금액 입력 받기
money = int(input(f"총 금액은 {total_price}원입니다. 입금해주세요: "))

# 거스름돈 계산하여 출력
change = money - total_price
print(f"거스름돈은 {change}원입니다.")

dic01.py

 

import os

# 파일들이 위치한 디렉토리 경로
dir_path = "D:\\python_sk\\static"

# 디렉토리 내 모든 파일 목록 가져오기
all_files = os.listdir(dir_path)

# .txt 확장자를 가진 파일들을 담을 리스트
txt_files = []

# 전체 파일 목록 출력 및 .txt 확장자를 가진 파일들을 따로 추출
print("전체 파일 목록")
for file in all_files:
    print(file)
    if file.endswith('.txt'):  # .txt 확장자를 가진 파일인지 확인
        txt_files.append(file)

# .txt 파일 목록 출력
print("txt 파일 목록:", txt_files)

# 각 .txt 파일을 열어서 내용 출력
for filename in txt_files:
    file_path = os.path.join(dir_path, filename)  # 파일의 전체 경로 생성
    with open(file_path, 'r', encoding='utf-8') as f:
        # 파일을 열고 내용 출력
        print(f"{filename}의 내용: {f.read()}")

file01.py

파일을 열 때 with open과 open이 있는데 open은 옛날 방식이라고 한다.

open으로 파일을 열면 close 해주지 않을 경우 메모리 누수가 발생할 수 있으므로 위 코드와 같이 with open을 사용하는 것을 권장한다.

 

이번에는 보안 요소를 생각하며 코딩해보자.

(소스코드에서 주석 처리된 정보들이 중요한 정보라고 가정함)

 

참고로 파이썬에서 리스트, 집합, 튜플의 구분은 다음과 같다.

실습에서는 set (=집합) 을 사용했다.

  • 가변적
    • list [] ← ‘-’오퍼레이터가 지원되지 않음
    • set {} ← ‘-’오퍼레이터가 지원됨, 중복X
  • 불변적
    • 튜플 ()

 

우선 새로 생성된 파일 내에 주석 처리되어 있는 라인이 있는지 검사한다.

import os
import time

# 파일 생성을 모니터링할 디렉토리 경로
DIR_WATCH = "static"

# 이전에 디렉토리에 있는 파일 목록
previous_files = set(os.listdir(DIR_WATCH))

# 무한 루프로 파일 모니터링을 진행
while True:
    time.sleep(1)  # 1초마다 확인
    print("모니터링중")
    
    # 현재 디렉토리에 있는 파일 목록 가져오기
    current_files = set(os.listdir(DIR_WATCH))
    
    # 새로운 파일들 찾기
    new_files = current_files - previous_files

    # 새로운 파일들을 확인하고 주석이 포함된 라인 출력
    for filename in new_files:
        print(f"====={filename}이 생성됨=====")
        file_path = os.path.join(DIR_WATCH, filename)
        
        # 새로 생성된 파일을 읽기 모드로 열고 내용을 확인
        with open(file_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            for num, line in enumerate(lines):
                if line.startswith("#") or line.startswith("//"):
                    # 주석으로 시작하는 라인일 경우 출력
                    print(f"{num+1}줄 주석 처리: {line}")

    # 이전 파일 목록을 현재 파일 목록으로 갱신
    previous_files = current_files

file02.py

 

 

새로운 파일에서 주석처리된 정보가 있으면 보안 담당자의 메일로 자동 통보한다.

# 파일 모니터링
# 새로운 파일에서 주석처리된 정보가 있으면
# 보안 담당자의 메일로 자동 통보
import smtplib # SMTP 라이브러리
from email.header import Header
from email.mime.text import MIMEText
from dotenv import load_dotenv
import os
import time

# 메일 보내기 함수
def mail_sender(file_path, line):
    # .env에서 정보 가져오기
    load_dotenv()
    SECRET_ID = os.getenv("SECRET_ID")
    SECRET_PASS = os.getenv("SECRET_PASS")

    # 접속 시도
    smtp = smtplib.SMTP('smtp.naver.com', 587)
    smtp.ehlo()
    smtp.starttls()

    # 로그인
    smtp.login(SECRET_ID, SECRET_PASS)

    # 보내는 메일, 받는 메일
    myemail = "@naver.com" # 보내는 메일 (보내는 메일의 아이디, 비밀번호 정보는 있어야 함)
    youremail = "@naver.com" # 받는 메일

    # 보내는 내용
    subject = f"파일 탐지 {file_path}" # 제목
    message = f"탐지된 결과 : {file_path} : {line}" # 내용

    # 인코딩
    msg = MIMEText(message.encode('utf-8'), _subtype='plain', _charset='utf-8')
    msg['Subject'] = Header(subject.encode('utf-8'), 'utf-8')
    msg['From'] = myemail
    msg['To'] = youremail
    smtp.sendmail(myemail,youremail,msg.as_string())
    smtp.quit()



DIR_WATCH = "static"

previous_files = set(os.listdir(DIR_WATCH))

while True:
    time.sleep(1)
    print("모니터링중")
    current_files = set(os.listdir(DIR_WATCH))
    new_files = current_files - previous_files

    for filename in new_files:
        print(f"====={filename}이 생성됨=====")
        file_path = os.path.join(DIR_WATCH, filename)
        with open(file_path,'r', encoding='utf-8') as f:
            lines = f.readlines()
            for num, line in enumerate(lines):
                #print(line.strip("\n"))
                if line.startswith("#") or line.startswith("//"):
                    print(f"탐지된 결과 : {file_path} : {line}")
                    mail_sender(file_path, line)
                    
    previous_files = current_files

mail01.py

위 코드는 .env 파일을 활용해 소스 코드 내에 개인 정보(아이디, 비밀번호)가 포함되지 않도록 하는 방법이다.

pip install python-dotenv 명령어로 패키지를 설치해  프로젝트의 환경 변수를 .env 파일에 정의하고, 코드에서 이를 로드하여 사용하면 된다.

(패키지 import문은 from dotenv import load_dotenv 이다.)

 

이처럼 개인정보, 즉 환경 변수는 .env 파일(=설정파일)을 생성해 몰아넣고 깃허브에 올라가지 않도록 해야 한다.

나의 경우 .env 파일의 내용은 다음과 같이 구성했다.

# .env 파일 내용
# 네이버 아이디, 비밀번호
SECRET_ID = "..."
SECRET_PASS = "..."

 

 

아래는 보안 담당자에게 통보할 때 해당 주석 내용이 무엇인지 파일을 함께 첨부해서 보내는 코드를 추가한 것이다.

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication 
from dotenv import load_dotenv
import os
import time

# 메일 보내기 함수
def mail_sender(file_path):
    # .env 파일에서 이메일 계정 정보 로드
    load_dotenv()
    SECRET_ID = os.getenv("SECRET_ID")
    SECRET_PASS = os.getenv("SECRET_PASS")

    # SMTP 서버 연결
    smtp = smtplib.SMTP('smtp.naver.com', 587)
    smtp.ehlo()
    smtp.starttls()

    # 이메일 계정으로 로그인
    smtp.login(SECRET_ID, SECRET_PASS)

    myemail = '@naver.com'  # 송신자 이메일 주소
    youremail = '@naver.com'  # 수신자 이메일 주소

    # 이메일 메시지 설정
    msg = MIMEMultipart()
    msg['Subject'] ="첨부파일 테스트 입니다."  # 이메일 제목
    msg['From'] = myemail  # 송신자 이메일
    msg['To'] = youremail  # 수신자 이메일

    text = """
    첨부파일 메일 테스트 내용 입니다.
    감사합니다.
    """
    contentPart = MIMEText(text) 
    msg.attach(contentPart) 

    # 첨부파일 설정
    etc_file_path = file_path
    with open(etc_file_path, 'rb') as f : 
        etc_part = MIMEApplication( f.read() )
        etc_part.add_header('Content-Disposition','attachment', filename=etc_file_path)
        msg.attach(etc_part)

    # 이메일 전송
    smtp.sendmail(myemail, youremail, msg.as_string())
    smtp.quit()

# 모니터링할 디렉토리 경로
DIR_WATCH = "static"
# 이전에 확인된 파일 목록
previous_files = set(os.listdir(DIR_WATCH))
# 주석이 포함된 라인을 저장할 파일
detected_files = "detected_files.txt"

while True:
    time.sleep(1)  # 1초마다 모니터링
    print("모니터링 중")
    current_files = set(os.listdir(DIR_WATCH))
    new_files = current_files - previous_files

    # 새로운 파일을 탐지하면
    for filename in new_files:
        file_path = os.path.join(DIR_WATCH, filename)
        # 파일을 열고 주석이 포함된 라인을 확인하여 detected_files에 저장
        with open(file_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            for line in lines:
                if line.startswith("#") or line.startswith("//"):
                    print(f"{file_path} 주석 처리된 라인: {line}")
                    # detected_files에 주석이 포함된 라인을 추가
                    with open(detected_files, 'a', encoding='utf-8') as wf:
                        wf.write(f"{file_path} 주석 처리된 라인: {line}")

        # 이메일로 detected_files를 전송
        mail_sender(detected_files)

    # 현재 파일 목록을 이전 파일 목록으로 업데이트
    previous_files = current_files

mail02.py

코드에서 알 수 있듯이 첨부파일을 보내기 위해서 MIMEMultipart()를 사용했는데, MIMEMultipart()는 MIME(Multipurpose Internet Mail Extensions) 형식의 이메일을 생성하기 위한 클래스이다.

 

꼭 첨부파일이 아니더라도 MIMEMultipart() 객체를 생성하면 이메일의 여러 부분을 조립할 수 있는데,

본문 텍스트, HTML 형식의 본문, 첨부 파일 등 여러 클래스가 존재하며 각각 MIMEText, MIMEImage, MIMEAudio, MIMEApplication 등의 클래스 네임을 사용하면 된다.