SK shieldus Rookies 16기 (클라우드 보안 기술 #05)

만두다섯개·2023년 12월 6일

SK 루키즈 16기

목록 보기
27/52

주요 정보

  • 교육 과정명 : 클라우드기반 스마트융합보안 과정 16기
  • 교육 회차 정보 : '23. 12. 06. 클라우드 보안 기술 #05

학습 내용 요약

  1. 웹 애플리케이션 환경 AWS 리소스로 구성
  • Flask 앱과 MySQL 사용방법(스키마 생성, 조회)
  • AWS EC2, RDS, MySQL 연동
  • React 앱 사용(웹 서버 실행 기초)
  • React 와 Flask 앱 사용, 그리고 CORS 오류
  • AWS 리소스(EC2, RDS)에 React, Flask 앱 구성
  • AWS 리소스(EC2, RDS, S3)에 React, Flask 앱 구성

1. 웹 애플리케이션 환경 AWS 리소스로 구성

  • 커스텀으로 설치를 선택한다.

  • 디폴트 설정으로 설치를 진행하면 계정 생성 설정 창이 나온다.
  • 루트 계정 : (? / password), 사용자 계정: ( rookies / password ) 를 설정한다.

  • MySQL 창이 나온다. 여기서 연결을 추가한다.

  • Username 에는 root, Password - Store in Vault... 로 들어가 패스워드를 입력한다.

-연결에 이상이 없다면 ok 를 누른다.

  • LocalMySQLServer 연결이 생겼다. 해당 연결로 들어가 보자.

  • 실습 참고 링크 : https://shanepark.tistory.com/64#google_vignette

  • 위 링크를 참조해 아래에서 DB를 생성한다. (MySQL 에서의 스키마 == DB 동일하다. 스키마 : 테이블의 집합체로써의 데이터베이스)

스키마 및 테이블 생성
CREATE SCHEMA `sampledb` DEFAULT CHARACTER SET utf8 ;

use sampledb;

CREATE TABLE `emp` (
  `empno` varchar(50) NOT NULL,
  `name` varchar(50) DEFAULT NULL,
  `department` varchar(50) DEFAULT NULL,
  `phone` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`empno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 번개 모양 아이콘을 누르면, SQL 문을 실행한다. 이상이 없다면 스키마를 생성한다.

  • 정상적으로 DB가 생성되었다.
  • 데이터를 삽입해 보자!
  • 좌측 상단의 SQL + 아이콘을 누르고, 샘플 데이터를 추가하는 SQL 문을 실행시켜보자

가상 터미널에서 Flask 앱 생성 환경 구성

  • CMD 창에서 hello-flask 디렉터리 생성 밎 이동한다.
  • 해당 위치에서 VS Code 프로그램을 실행한다.

  • 그 후 현 디렉토리에 venv 이름의 가상환경을 생성한다.

  • 가상 터미널에 진입한다. 아래 명령어를 입력 후 엔터를 누르면 셀이 (venv) 라고 변경된다.

.\venv\Scripts\activate
  • 가상 터미널에서 flask 를 설치한다.

  • 추가로 pymysql 도 설치해준다.

(venv) C:\aws\hello-flask>pip install pymysql
  • flask 설치가 완료되면 설치 목록을 확인한다.

  • 이 내용을 txt 파일에 기록해 두자. 추후 앱 환경이 변경되어도, 동일한 패키지 구성을 만들 수 있다.

  • 이전에 열었던 VS Code에서 app.py라는 파일을 생성 후 아래 코드를 입력한다.
import pymysql #mysql을 python에서 사용할 수 있는 라이브러리

class MyEmpDao:
    def __init__(self):
        pass
    
    def getEmps(self):
        ret = []
        db = pymysql.connect(host='localhost', user='root', db='sampledb', password='password', charset='utf8')
        curs = db.cursor()
        
        sql = "select * from emp";
        curs.execute(sql)
        
        rows = curs.fetchall()
        for e in rows:
            temp = {'empno':e[0],'name':e[1],'department':e[2],'phone':e[3] }
            ret.append(temp)
        
        db.commit()
        db.close()
        return ret
    
    def insEmp(self, empno, name, department,phone):
        db = pymysql.connect(host='localhost', user='root', db='sampledb', password='password', charset='utf8')
        curs = db.cursor()
        
        sql = '''insert into emp (empno, name, department, phone) values(%s,%s,%s,%s)'''
        curs.execute(sql,(empno, name, department,phone))
        db.commit()
        db.close()
    
    def updEmp(self, empno, name, department,phone): 
        db = pymysql.connect(host='localhost', user='root', db='sampledb', password='password', charset='utf8')
        curs = db.cursor()
        
        sql = "update emp set name=%s, department=%s, phone=%s where empno=%s"
        curs.execute(sql,(name, department, phone, empno))
        db.commit()
        db.close()
    def delEmp(self, empno):
        db = pymysql.connect(host='localhost', user='root', db='sampledb', password='password', charset='utf8')
        curs = db.cursor()
        
        sql = "delete from emp where empno=%s"
        curs.execute(sql,empno)
        db.commit()
        db.close()

if __name__ == '__main__':
    #MyEmpDao().insEmp('aaa', 'bb', 'cc', 'dd')
    #MyEmpDao().updEmp('aa', 'dd', 'dd', 'aa')
    #MyEmpDao().delEmp('aaa')
    emplist = MyEmpDao().getEmps();
    print(emplist)



  • VS Code - 파일 - 저장 : 위에서 생성한 파일을 저장한다
  • 그리고 가상 환경에서 아래 명령어를 입력한다
python app.py

  • 실제로 이 코드를 실행하면 getEmps 메서드가 호출되어 'emp' 테이블의 모든 레코드가 콘솔에 출력을 확인 가능하다.

  • 다른 파이썬 파일을 VS Code 에서 생성하자. 이름은 mydao.py이다.

import pymysql

class MyEmpDao:
    def __init__(self):
        pass
    
    def getEmps(self):
        ret = []
        db = pymysql.connect(host='localhost', user='root', db='sampledb', password='password', charset='utf8')
        curs = db.cursor()
        
        sql = "select * from emp";
        curs.execute(sql)
        
        rows = curs.fetchall()
        for e in rows:
            temp = {'empno':e[0],'name':e[1],'department':e[2],'phone':e[3] }
            ret.append(temp)
        
        db.commit()
        db.close()
        return ret
    
    def insEmp(self, empno, name, department,phone):
        db = pymysql.connect(host='localhost', user='root', db='sampledb', password='password', charset='utf8')
        curs = db.cursor()
        
        sql = '''insert into emp (empno, name, department, phone) values(%s,%s,%s,%s)'''
        curs.execute(sql,(empno, name, department,phone))
        db.commit()
        db.close()
    
    def updEmp(self, empno, name, department,phone): 
        db = pymysql.connect(host='localhost', user='root', db='sampledb', password='password', charset='utf8')
        curs = db.cursor()
        
        sql = "update emp set name=%s, department=%s, phone=%s where empno=%s"
        curs.execute(sql,(name, department, phone, empno))
        db.commit()
        db.close()
    def delEmp(self, empno):
        db = pymysql.connect(host='localhost', user='root', db='sampledb', password='password', charset='utf8')
        curs = db.cursor()
        
        sql = "delete from emp where empno=%s"
        curs.execute(sql,empno)
        db.commit()
        db.close()

if __name__ == '__main__':
    #MyEmpDao().insEmp('aaa', 'bb', 'cc', 'dd')
    #MyEmpDao().updEmp('aa', 'dd', 'dd', 'aa')
    #MyEmpDao().delEmp('aaa')
    emplist = MyEmpDao().getEmps();
    print(emplist)
  • 이 mydao.py를 실행시키면, 로직을 생성해준다.
  • 해당 파일의 실행 결과는 getEmps 메서드가 호출되어 'emp' 테이블의 모든 레코드가 콘솔에 출력된다.

  • ello-flask 안에 templates 파일을 생성하고, 여기에 emp01.html도 생성한다.
<!DOCTYPE html>
<html>
<head>
<style>
table, th, td{
	border : 1px solid black;
}
</style>
<meta charset="UTF-8">
<title>회원관리</title>
</head>
<body>
<table>
	<tr>
		<th>사번</th>
		<th>이름</th>
		<th>부서</th>
		<th>전화번호</th>
	</tr>
		{% for emp in empList%}
		<tr>
			<td>{{emp.empno}}</td>
			<td>{{emp.name}}</td>
			<td>{{emp.department}}</td>
			<td>{{emp.phone}}</td>
		</tr>
		{% endfor %}
</table>
</body>
</html>
  • 위 HTML 파일은 별도로 직접 실행하지 않는다. 아래에서 계속 확인해 보자.
  • 다음은 myflask01.py 을 추가한다.
from flask import Flask
from flask.templating import render_template

from mydao import MyEmpDao

app = Flask(__name__)

@app.route('/emp01')
def emp():
    empList = MyEmpDao().getEmps();
    return render_template("emp01.html", empList=empList)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)
  • 이제 총 4개의 파일이 저장되었다.
  • 이제 마지막으로 추가한 파일을 실행한다.
python myflask01.py

AWS 에서 만들어보자

VPC 생성

  • VPC 생성에서 VPC, 서브넷, 라우팅 테이블 등을 한번에 만들자.
  • VPC 생성 - VPC 생성 등 을 선택한다.

  • VPC 이름 : My-VPC-015
  • 아래 그림과 같이 배치도가 만들어질 것이다

RDS 생성

  • DB를 생성해보자.
  • RDS - 데이터베이스 생성
  • 표준 생성 - MariaDB 를 선택한다. 버전은 그대로 사용한다.

  • 템플릿 설정 항목을 보자.

  • 템플릿은 프리티어를 선택한다.

  • 설정 항목을 보자.

  • DB 인스턴스 식별자 : database-015 / 마스터 사용자 이름 : root / 마스터 암호 : password

  • 인스턴스 구성 항목을 보자

  • DB 인스턴스 구성 : db.t2.micro 선택한다

  • 아래 DB 인스턴스 종류를 확인하면, 앞에서 프리티어를 선택해 프리티어 DB 인스턴스만 선택 가능함을 확인 가능하다.

  • 이제 연결 항목으로 넘어간다.

  • 앞서 생성한 VPC를 사용하고, 가용 영역을 ap-southeast-1a,b,c 중 임의로 선택(나는 a를 선택했다.)

  • 이제 생성 버튼을 눌러 DB를 생성한다.

EC2 Instance 생성

  • 인스턴스를 생성해보자.
  • 인스턴스 이름: MyEC2-015 / EC2 인스턴스 : t2.micro / 키 페어 : MyKeyPair-015
  • 네트워크 설정 : 위에서 생성한 VPC 사용한다. 서브넷은 퍼블릭 서브넷 중 RDS 인스턴스를 배치한 AZ에 생성된 서브넷을 선택한다.
  • 해당 인스턴스는 SSH(TCP/22), HTTP(TCP/80) 2개를 사용하는 인바운드 보안 그룹 규칙을 가지게 설정한다.

RDS EC2 Instace 접근 설정

  • RDS - 생성한 DB - 연결 및 보안 - 연결된 컴퓨팅 리소스 - 생성한 EC2 인스턴스를 추가한다.

  • RDS 의 보안 그룹에 EC2 인스턴스가 추가되고, EC2 보안그룹에는 RDS가 추가된다고 안내하고 있다. 설정을 눌러 추가시킨다.

EC2 연결해보자

  • Bitvise SSH 프로그램으로 EC2 인스턴스에 접속해보자
  • 나는 싱가포르 리전에서 다시 키를 만들었기 때문에 Clinet key manager 에서 생성한 키를 추가한다.
  • EC2 인스턴스 퍼블릭 IP (22번 포트) / Username : ec2-user / 로 접속한다.

EC2 연결 응답 없음 오류

  • 싱가포르 리전에서 리소스 사용 -> 서울 리전으로 변경. EC2 인스턴스 생성 및 접속 시, 오류가 발생하였다. (SSH 연결 시도 시, 응답없음) -> 계정의 엑세스 키 삭제 후 재발급. 재접속하였음. 뭔가 리전 변경 이후 Resource 생성 과정에서 딜레이나 오류 발생하거나 AWS 리소스는 정상적으로 생성 되었는데 내 컴퓨터에서 접속을 못한게 아닐까 예상함(Bitvise 프로그램, AWS CLI 2개 사용했음.)

  • EC2 접속 후 파이썬 설치를 확인한다.

  • Bitvise 의 SFTP 기능으로 파일을 업로드 하자

  • EC2 에서는 hello-flask 폴더를 하나 생성한 후, 그 안에 업로드한 파일을 넣는다.

  • 다시 접속한 EC2 터미널로 돌아가자.
  • PIP 및 프로그램 실행에 필요한 패키지를 설치
[ec2-user@ip-10-0-1-218 hello-flask]$ sudo yum install python-pip

[ec2-user@ip-10-0-1-218 hello-flask]$ pip install -r requirements.txt
  • 애플리케이션을 실행
    [ec2-user@ip-10-0-1-218 hello-flask]$ python3 mydao.py

  • local 호스트 관련 에러가 발생한다. 이것을 해결해보자.
  • 실행 환경에 맞도록 DB 접속 정보를 변경 ⇒ host 정보를 localhost 대신 데이터베이스 인스턴스의 엔드포인트로 변경
  • RDS 의 엔드포인트 확인한다.

  • 위의 엔드포인드 주소를 현재 EC2 내의 mydao.py에서 수정하자.
  • VS Code 에서 local 을 RDS 엔드포인트 주소로 변경해주었다.
  • 다시 mydao.py 를 업로드 해준다.

  • 다시 실행 해 준다.

  • Unkown database 'sampledb'라고 나온다. RDS DB 인스턴스로 연결이 필요하다.

  • MySQL 워크벤치를 이용해서 RDS의 데이터베이스 인스턴스로의 연결을 생성한다.

  • MySQL 워크벤치로 이동해 새로운 연결을 생성한다.

  • EC2 정보와 RDS 정보를 입력하고 Test Connection 한다. RDS password를 물어본다. 그러면 password 입력 후 경고를 무시하고 진행하면 연결 성공이 나온다.

  • ok 를 눌러 MySQL 연결을 생성하자.
  • 이제 생성한 연결로 들어간다.
  • 스키마와 테이블을 생성한다.
CREATE SCHEMA `sampledb` DEFAULT CHARACTER SET utf8 ;

use sampledb;

CREATE TABLE `emp` (
  `empno` varchar(50) NOT NULL,
  `name` varchar(50) DEFAULT NULL,
  `department` varchar(50) DEFAULT NULL,
  `phone` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`empno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  • sampledb 라는 스키마를 추가해 결과를 Schema - 새로고침 후 확인해보자.
  • 생성한 sampledb 스키마에 데이터를 추가하고, 확인해보자.

  • EC2와 RDS 연결 및 스키마를 생성했다.
  • EC2 터미널로 이동해 다시 mydao.py 를 실행해 보자

  • mydao.py를 실행하니 스키마인 sampledb로부터 데이터를 잘 가져온다!

  • myflask01.py를 실행해 보자.

  • 80번 포트를 사용하려고 해서 권한 문제가 발생한다.

sudo pip install flask
  • 다시 myflask01.py를 실행해 보자
  • 이번엔 pymysql 모듈을 가져오지 못한다고 한다. 아래 명령어로 pymysql 설치해주자.
sudo pip install pymysql

  • 앱이 잘 실행되고 있다!

웹 애플리케이션을 SPA 형태로 변경

  • SPA란? Single Page Application, 단일 페이지 앱
  • 해당 과정은 Flask 앱 으로부터 JSON 데이터를 가져와 React에서 사용하려고 한다. 이와 관련된 CORS 오류도 논의한다.

Flask 앱 실행

  • 윈도우 아이콘을 클릭하면 다운로드가 진행된다
  • 새 명령 프롬프트를 실행해서 설치를 확인
C:\Users\USER> node --version
C:\Users\USER> npm --version

  • 설치가 잘 된것을 확인 가능하다!

  • create-react-app 설치해보자. ( Create React App을 전역 설치 도구이다)

C:\Users\USER> npm install create-react-app -g
  • 설치가 완료되었다면 /aws/ 위치로 가서 hello-react 를 실행시켜 보자.

  • 마지막 문장은 npx 패키지를 설치하며 hello-react 라는 앱을 생성한다.

  • 리액트 앱이 성공적으로 설치되면 해당 폴더로 이동한다

  • 통신 모듈을 설치하고, VS Code 를 연다. 그리고 개발 서버를 실행한다.

  • 리액트 앱이 실행된 것을 확인 가능하다.

Flask 앱으로부터 받은 데이터 사용하는 React 앱 생성

-Flask 앱 으로부터 JSON 데이터를 가져와 사용해보자.

  • flask의 템플릿 파일의 내용을 플라스크의 App.css 파일과 App.js 파일로 이식한다.
  • 위에서 개방한 VS Code 에서 리액트 앱의 내용을 수정한다.
  • 먼저 App.css 파일을 수정한다.
table, th, td{
  border : 1px solid black;
}

  • 그 다음은 App.js 를 수정한다
  • VS Code 를 저장한다.

Flask 데이터 요청 방식 변경

  • Flask 앱이 데이터 요청 시, JSON 형식으로 데이터를 가져오게 해 보자.
  • 이 과정에서 아래처럼 JSON/Pared 방식처럼 나오게 하려면, 이전에 작업했던 VS Code를 원복 시켜야 한다. 즉, 처음 Flask 로 웹 브라우저에서 보이게 했던 상태에서 아래 과정을 거쳐야 한다.
  • 원복과정은 생략한다.
    /emp01 주소로 요청이 들어왔을 때 화면(template) + 데이터를 반환하지 않고 데이터만 반환하도록 수정해보자.
  • 아래 내용으로 -myflask01.py를 수정하자
from flask import Flask
from flask.templating import render_template
from flask.json import jsonify

from mydao import MyEmpDao

app = Flask(__name__)

@app.route('/emp01')
def emp():
    empList = MyEmpDao().getEmps()
    # return render_template("emp01.html", empList=empList)
    return jsonify(empList)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

  • 터미널에서 myflask01.py를 실행해 결과를 확인해보자.

  • 크롬 웹 브라우저의 JSON Formatter 확장 프로그램을 설치해 raw 와 prased 로 확인 가능하다.

Flask 앱으로부터 React 데이터 가져오기

  • Flask 앱이 데이터 요청 시, JSON 형식으로 데이터를 가져온다.
  • React 앱에서 Flask 앱에서 응답으로 가지고 있는 JSON 데이터를 가져오게 해보자.
  • 위에서 수정한 Flask 내용을 가져온다.
  • React의 App.js (컴포넌트)가 화면에 최초 나타났을 때(마운트될 때) flask 서버로 데이터를 요청하고, 응답으로 데이터가 전달되면 화면에 출력하도록 수정한다.

  • Flask는 VS Code 에서 수정 후, 저장 및 재실행 한다.
  • React 앱 결과를 확인하자
  • 해당하는 오류를 확인하면, 아래와 같다.
localhost/:1 Access to XMLHttpRequest at 'http://localhost/emp01' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 
  • React 앱에서는 Flask 요청으로부터 데이터를 가져오려는 요청을 생성한다.
  • 그러나 이는 브라우저의 CORS 정책 위반으로 해당 요청의 발송을 제한한다.
  • CORS (Cross-Origin Resource Sharing)이란? 원본 교차 리소스 공유로, 웹 페이지가 해당 웹 페이지를 제공한 도메인과 다른 도메인으로의 요청을 보내는 것을 제한한다.
  • Flask 에서는 'flask_cors' 라이브러리를 사용해 이를 허용 할 수 있다.
  • 주석처리된 두 문장은, CORS 객체를 생성해 Flask의 app에서 모든 도메인으로부터의 요청을 허용한다는 의미이다.
  • 두 문장의 주석 처리를 삭제하고 다시 저장 및 실행하면, 결과가 아래와 같다.


브라우저가 Flask로 데이터 요청하는 것이 CORS 정책 오류로 인해 제한되고, 이로 인해 데이터를 가져올 수 없어 빈 테이블이 출력된 것이다.

SPA 형태의 웹 앱 AWS 배포

React 서버 EC2 Instace 생성

퍼블릭 서브넷에 React Server(app)을 담기 위해 인스턴스 생성

  • EC2 이름 : MyReactServer-015
  • 키 페어 : MyKeyPair-015 (해당 포스트 실습에서 생성한 키페어사용)
  • 네트워크 설정은 아래와 같지 않다. ( 퍼블릭 서브넷 c 로 사용)

Bitvise 로 해당 인스턴스 SSH 접속

  • 웹 서버 설치 및 React 코드가 웹 서버를 통해 서비스되도록 설정한다.
  • 인스턴스 설정 확인

React 앱에서 EC2 Instance 퍼블릭 주소 접근 설정

function App() {
  // 상태변수를 정의
  const [emps, setEmp] = useState([]);  
  
  // 화면이 최초로 출력되었을 때(=마운트될 때) 동작을 정의 
  useEffect(() => {
        // axios.get('http://localhost:80/emp01')
    // 플라스크 서버가 동작하고 있는 EC2 인스턴스의 퍼블릭 주소로 변경
    axios.get('http://13.212.152.43/emp01')
    .then(res => {
      console.log(res);   // 응답 내용을 콘솔에 출력
      setEmp(res.data);   // 응답 내용 중 data 항목을 상태변수 emps에 설정
    })
    .catch(err => console.log(err));
  }, []);

  return (
    <table>
      <tr>
        <th>사번</th>
        <th>이름</th>
        <th>부서</th>
        <th>전화번호</th>
      </tr>
        { // 상태변수의 내용을 화면에 출력
          emps.map(emp => 
            <tr>
              <td>{emp.empno}</td>
              <td>{emp.name}</td>
              <td>{emp.department}</td>
              <td>{emp.phone}</td>
            </tr>            
          )
        }        
    </table>
  );
}

React 코드 빌드 및 EC2 업로드

  • React 코드를 빌드한다. 나는 새 프롬프트로 접속하니까 아래 명령어가 실행됐다.
C:\aws\hello-react> npm run build

C:\aws\hello-react> dir .\build
  • EC2 업로드

Flask 코드 업데이트

  • Flask Server EC2 생성은 이전에 생성한 인스턴트 MyEC2-015 이다.
  • 아래 코드처럼 CORS 보안 정책 허용한 수정 코드만 인스턴스에 업로드한다.
from flask import Flask
from flask.templating import render_template
from flask.json import jsonify
from flask_cors import CORS

from mydao import MyEmpDao

app = Flask(__name__)
CORS(app, origins=['*'])


@app.route('/emp01')
def emp():
    empList = MyEmpDao().getEmps()
    # return render_template("emp01.html", empList=empList)
    return jsonify(empList)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

  • 이제 인스턴트의 리액트 앱의 퍼블릭 주소로 접근하면 인스턴트의 퍼블릭 앱으로부터 데이터를 가져와 보여준다.

React 서버를 S3 정적 웹 사이트 호스팅 기능을 이용해 구현

  • 기존 리액트 인스턴트 종료
  • 아래 구성과 같이 버킷 생성

  • 리액트 앱 빌드 파일을 버킷에 업로드 한다(업로드 버튼을 누르기 전에 아래 권한도 추가)
  • 권한을 아래와 같이 설정 후 업로드 한다

  • 업로드 완료 후, 버킷 속성 - 정적 웹 사이트 호스팅 - 편집 - 아래와 같이 설정한다

  • 이제 다시 해당 옵션으로 들어가면, DNS 주소가 나온다.

-이를 접속해보면 아래와 같이 성공한다!

  • 다음 강의를 위해 RDS (임시 정지 7일), EC2 (중지) 시킨다.
profile
磨斧爲針

0개의 댓글