[ TWIL 13주차 ]

박재영·2020년 8월 3일
2

코드스테이츠 46,47일차 (08월 03일, 08월 04일)

Cloud

    - 클라우드는 인터넷 환경에서 제공되는 가상컴퓨터, 가상서버다. 클라우드 서비스는 특정 업체에서 여러 대의 컴퓨터를 구비해 둔 후 일정 금액을 내면 이 컴퓨터들을 인터넷을 통해 원격으로 이용할 수 있도록 해준다.

    - AWS의 경우 원격접속은 터미널에서 ssh 프로토콜을 사용해서 가능하다. 터미널이라는 작은 창에서 터미널을 실행시키고 있는 내 컴퓨터가 아닌 저 멀리 있는 컴퓨터를 접속한다는 건 신기하고 멋진 일이다!

Deploy

    - 배포 : 클라우드 서비스에서 가상컴퓨터 한 대 빌리고 가상컴퓨터에 OS 설정, cpu, memory 등을 셋팅 후 소프트웨어 코드를 올려서 구동시키는 것

AWS Services

1. S3 (Simple Storage Service)

    - 파일 서버, 정적 웹사이트 호스팅 제공(버킷에 들어있는 build파일을 바탕으로 웹페이지 호스팅)

    - 버킷 : 디렉토리, 폴더, 리전별로 유일한 이름을 가져야 한다.

    - 객체 : 이미지파일, 소스파일 등을 가리킴. URL로 접근이 가능

2. EC2 (Elastic Compute Cloud)

    - 가상 서버를 제공해주는 서비스

    - 인스턴스 : 독립된 가상 서버

    - OS, cpu, memory 등을 선호에 맞게 선택할 수 있다.

접속 방법

  1. 인스턴스 생성시 .pem 파일에 권한 변경 (단 한번 설정)
    chmod 400 example.pem
  2. ssh 프로토콜을 사용하여 ec2 서버 주소로 접속
    ssh -i "example.pem" example@ip주소

    - 원격접속 후 github 소스를 다운 받고 모듈 설치 및 config 설정 후 서버를 실행시킨다. 이때 터미널을 종료해도 서버가 돌아가도록 pm2 모듈을 사용한다.

3. RDS (Relational Database Service)

    - 관계형 데이터베이스 설치 및 관리를 편리하게 할 수 있도록 도와주는 서비스

    - 데이터베이스 생성과정에서 설정한 계정, 비번 그리고 생성 후 부여받은 앤드포인트를 가지고 워크밴치 등 GUI 툴에 접속하여 DB 관리를 할 수 있다.

코드스테이츠 48일차 (08월 04일)

     오늘 마지막 HA를 치뤘다. 이때까지 배웠던 클라이언트, 서버, DB를 총망라 다뤘다. 개별적으로 보면 크게 어렵진 않지만 전체를 다룬다는 생각에 긴장을 해서 그런지 머리 회전이 잘 안 돌아갔다. 언제나 그렇듯 너무 크게 보면 혼란스러움만 가중되기에 서버부터 테스트를 통과하자하며 다른 데 신경 안쓰고 서버부분만 신경썼다. DB는 이미 model이 생성되어있고 그 안을 채우기만 하면 끝이였다. 의문이 든 건 migration 폴더 없어도 DB가 제대로 작동된다는 점이다. DB를 mysql이 아닌 sqlite를 썼던데 이 경우는 migration 없어도 되는 건가? 서버와 클라이언트 모두 다 테스트를 통과한 후 브라우저 상에서 잘 되는지 확인해봤다. 이상하게도 postman 이랑 잘 만 주고 받았던 http 통신이 에러가 난다. network 탭에서 서버로부터 set-cookie를 잘 받긴 한데, 클라이언트는 cookie 헤더에 담아 보내지 않고 있다. 이거 때문에 시간을 많이 잡아먹었다. 알아 본 결과 cors 정책 문제였다. 오리진이 다른 경우 cookie를 헤더에 적용하려면 반드시 서버와 클라이언트 모두 credential을 true로 설정해야 한다. 겨우 6시 전에 과제를 제출했다. 마무리로 시연영상을 찍는데 하하... 첨엔 어색해서 말을 중간 중간 버벅거리기도 했지만 그동안 페어프로그래밍을 하면서 쌓아왔던 내공으로 무사히 완수했다.

     다음주 부터는 프로젝트 기간이다. 프로젝트 동안 내가 갖고 가야 할 것들을 정리하면 아래와 같다.

  1. TDD : 테스트를 먼저 작성하고 기능 구현하기
  2. 수도코드 : 나중에 프로젝트 발표할 때 엄청 유용하다
  3. Git 사용법 : 커밋메세지, 브랜치 머지 등 익숙해지기
  4. 기록 : 프로젝트 동안 새로 알게 된 내용 + 직면한 문제와 해결과정 + 팀원들과의 논의내용 등 꼼꼼하게 기록하기. 자발적으로 서기가 되겠다는 마음가짐. 기록의 중요성은 자소서를 작성할 때 발휘한다.

Solo Week

SSL 적용하기

     그동안 http로 로그인을 구현했다. http는 비밀번호와 같이 문자열이 있는 그대로 노출되기 때문에 보안문제가 발생한다. 연습 삼아 만들 때는 문제가 없지만 앞으로 애플리케이션을 배포하거나 취직했을 때 맞닥뜨릴 테니 미리 경험해 보는 게 좋을 것 같다는 생각이 들었다.

     ssl에 대한 간단한 개념설명을 하자면 아래와 같다.

- ssl은 공개키와 대칭키를 혼합한 방식이다.
- 대칭키는 하나의 키(비밀번호)로 데이터를 암호화/복호화하는 방식
- 공개키는 두 개의 키를 사용하여 암호화/복호화하는 방식. 가령, A와 B키가 있다고 할 때 A로 암호화하면 B로 복호화할 수 있고 B로 암호화하면 A로 복호화할수 있다.
- ssl 동작과정: 브라우저가 사이트에 접속하면 랜덤데이터를 생성하고 서버에 사용가능한 암호화방식을 전달한다. 서버는 CA(인증서를 제공하여 통신을 보증하는 제 3자)에 서비스 정보와 공개키를 전달하여 인증서를 받는다. 받은 인증서와 함께 클라이언트와 합의한 암호화방식, 생성한 랜덤데이터를 브라우저에 전달한다. 브라우저는 CA 리스트에서 인증서 제공 CA를 찾아 해당 CA의 공개키로 인증서를 복호화한다. 복호화가 성공하면 브라우저가 생성한 랜덤데이터와 서버가 생성한 랜덤데이터를 가지고 pre master secret을 생성한다. pre master secret을 인증서에 들어있는 서버의 공개키로 암호화하여 서버에 전달한다. 서버는 암호화된 pre master secret을 비밀키로 복호화하여 세션 key로 저장한다. 이후 서버와 브라우저가 데이터를 주고 받을 때 대칭키(pre master secret)을 사용하여 암호화/복호화한다. 데이터 전송이 완료되었으면 ssl 통신 완료를 서로 알리고 세션 key를 폐기한다.

greenlock-express

    - greenlock-express : nodeJS에서 ssl을 무료로 이용할 수 있는 모듈이다.

    - npx greenlock init --config-dir ./greenlock.d --maintainer-email 'jon@example.com' 를 터미널에 입력하면 자동으로 server.js, app.js, greenlock.d/config.json, .greenlockrc이 생성된다.

    - npx greenlock add --subject example.com --altnames example.com,www.example.com 를 터미널에 입력하면 https 사용할 도메인 주소를 등록한다. local에서 작업할 경우 ngrok을 사용해서 https url 주소를 입력한다.

Redis

    - In-memory 데이터베이스, 주로 세션 id 저장할 때 사용한다

    - nodemon으로 서버를 재실행하면 기존에 session store에 저장된 세션 id들이 삭제 되어 불편하다. 따로 store를 지정하지 않으면 default로 in-memory로 저장되기 때문이다(이때, in-memory는 실행 중인 서버의 memory다). redis 서버의 in-memory에 저장하면 서버의 memory와 독립적으로 작동하며 redis 서버를 끄지 않는 한 설정해 둔 만료기간 만큼 session id들을 유지할 수 있다.

    - <key-value> 형태로 저장된다. key는 session id, value는 cookie 정보다.

// redis 서버 접속
// get <key> 로 value값을 확인할 수 있다. 
127.0.0.1:6379> get sess:RNLyzSf4t7aVtkEjg1Atrm0Z9usPj_5k
"{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"secure\":true,\"httpOnly\":true,\"path\":\"/\"},\"num\":1}"
const session = require('express-session');
const redis = require('redis'); // redis의 client로 app 서버 지정
const RedisStore = require('connect-redis')(session); // session과 redis 연결  

const redisClient = redis.createClient(6379, 'localhost'); // redis 서버의 포트, host 값을 인자로 넘긴다. 
const app = require('express')();

const sessOption = {
  name: 'sessionId',
  secret: 'keyboard cat',
  resave: true,
  saveUninitialized: false,
  cookie: {},
  store: new RedisStore({
    client: redisClient,
    ttl: 200,
  }),
};

app.use(session(sessOption));

helmet

    - 보안 관련 HTTP 헤더를 자동으로 설정해주는 미들웨어, 총 11개의 미들웨어를 종한 wrapper 함수


const express = require('express');
const helmet = require('helmet');

const app = express();

// 모든 기능을 다 적용
app.use(helmet());

// 특정 기능만 적용 
app.use(helmet.hsts()); 

// 특정 기능 제외 
app.use(
  helmet({
    frameguard: false,
  })
);

    - 각각의 헤더가 어떤 기능인지 궁금하다면 알아둬야 할 HTTP 공통 & 요청 헤더 by. zerocho, 프로덕션 우수 사례:보안 by. Express 공식문서 를 참조하자.

'use strict';
/* dev modules */
const path = require('path');
const express = require('express');
const logger = require('morgan');
const cors = require('cors');
const session = require('express-session');
const redis = require('redis');
const RedisStore = require('connect-redis')(session);
const helmet = require('helmet');

const redisClient = redis.createClient(6379, 'localhost');

const app = express();

app.use(helmet());

app.use(
  cors({
    origin: '*',
    methods: ['GET', 'POST'],
    credentials: true,
  })
);

var sessOption = {
  name: 'sessionId',
  secret: 'keyboard cat',
  resave: true,
  saveUninitialized: false,
  cookie: {},
  store: new RedisStore({
    client: redisClient,
    ttl: 200,
  }),
};

 // 배포 환경일 때만 적용 
if (app.get('env') === 'production') {
  app.set('trust proxy', 1);  // secure과 같이 사용 
  sessOption.cookie.secure = true; // HTTPS 사용시 설정 : HTTPS일 때만 set-cookie 헤더 전송
}

app.use(session(sessOption));
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

app.get('/info', function (req, res) {
  if (req.session.num === undefined) {
    req.session.num = 1;
    req.session.cookie.maxAge = 3000; // 3초 동안만 쿠키 사용, 3초 후 새로운 세션 id 부여받음  
  } else {
    req.session.num += 1;
  }
  res.status(200).json({ num: req.session.num });
});

module.exports = app;

if (require.main === module) {
  app.listen(8080, 'localhost', () => {
    console.log('server listening on http://localhost:8080');
  });
}

umzug

    - 프로그래밍적으로 migration을 할 수 있도록 돕는 툴이다.

    - 전에 sync({force:true})를 사용해서 변경된 모델을 반영하는 방법을 썼는데 찾아보니, 기존 데이터를 삭제하기 때문에 프로덕션용으로는 적합하지 않았다.
Sequelize.js: how to use migrations and sync

    - umzug는 ssh로 원격접속하여 서버를 구동시켜야 할 때 유용하다. cli처럼 데이터베이스에 저장된 데이터는 유지하면서 migrateion 파일을 바탕으로 변경사항을 반영해준다.

const { Sequelize } = require('sequelize');
const { Umzug, SequelizeStorage } = require('umzug');

const sequelize = new Sequelize({ dialect: 'sqlite', storage: './db.sqlite' });

const umzug = new Umzug({
  migrations: {
    path: './migrations',
    params: [
      sequelize.getQueryInterface()
    ]
  },
  storage: new SequelizeStorage({ sequelize })
});

(async () => {
  // migrations파일을 체크하고 만약 DB에 반영이 안 되었다면 실행시키다.
  await umzug.up();
})();

1개의 댓글

comment-user-thumbnail
2020년 8월 10일

팀장님 프로젝트도 화이팅이요 :)

답글 달기