안녕하세요 미루고 미루고 미루다 ( 끝 없는 배포의 연장으로 진행이 안된 상태로 이렇게 시간이... ㅜ ) 

 

최근 다시 데드라인이 다가오고 있기 때문에 급하게 관련 작업들을 진행을 하고 있습니다.

 

사실 FE쪽 디자인은 Antd를 사용하였는데 해당 포스팅은 다음 포스팅에 하도록 하고 ( 사용이 어렵지 않기 때문 )

 

Redux를 통한 전역 상태 관리에 대해서 우선 작성하게 되었습니다. Redux와 Hooks를 너무 오랜만에 사용하다보니 

 

기억이 너무 가물가물 했기 때문입니다

 

1차적으로는 모두의 룰인 Counter 입니다. Counter 작성 후 개발중인 페이지의 전역 상태 관리로 바로 넘어가겠습니다.

 

Store

React-Redux와 관련 @types 및 Redux의 내용들은 많은 검색을 통해 알수 있기 때문에 과감하게 생략 하겠습니다. 사용성 및 작성 순서대로만

 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import App from './App';
import rootReducer from './redux';


const store = createStore(rootReducer);

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
        <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

1차적으로 react-redux의 Provider를 받아와서 App 을 통해 전달 합니다.

* 한 개의 프로젝트는 단 하나의 스토어만 가질수 있으며 스토어 안에는 애플리케이션의 현재 상태 및 리듀서가 있습니다.

Store에 있는 상태값을 가져와서 사용하는 컴포넌트라면 아래의 순서를 거치게 됩니다.

 

컴포넌트 ( 1. 스토어를 통해 상태 값을 받아옴 ) 

컴포넌트 ( 2. 받아온 상태값으로 렌더 ) 

컴포넌트 ( 3. 상태 변경을 위해서 액션 디스패치 -> 스토어가 변경됨 )

컴포넌트 ( 4. 스토어의 상태가 변경되면서 다시 컴포넌트를 재 렌더링 )

 

액션 : 상태 변화시 발생하는 것

액션 생성 : 액션을 만들어서 상태 변화시 사용

리듀서 : 변화를 일으킴 액션을 파라미터로 받아서 새로운 상태를 만든 후 반환 

디스패치 : 액션을 발생 시킴 디스패치가 호출시 스토어가 리듀서 함수를 실행시켜서 새로운 상태를 만듬

 

여러가지의 상태를 사용하기 위해서는 리듀서를 여러개 사용하여야 하기 때문에 Combine 함수를 통해 rootReducer를 만든 후 해당 리듀서를 Store 에 전달합니다.

 

( rootReducer )

import { combineReducers } from 'redux';

const rootReducer = combineReducers({

});

export default rootReducer;

( CounterReducer )

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASAE';


interface counterAction {
  type:string;
}
export const increase = () =>({type:INCREASE});
export const decrease = () =>({type:DECREASE});

const initialState = {
  number : 55
}

function counter(state = initialState,action:counterAction){
  switch(action.type){
    case INCREASE:
      return{
        number:state.number + 1
      };
    case DECREASE:
      return{
        number:state.number -1
      };
    default:
      return state;
  }
}

export default counter;

( App 에서 hooks를 통해 state를 selector ) 

import React from 'react';
import './App.css';
import ReduxComponent from './ReduxComponent'
import { useSelector } from 'react-redux';
import { increase, decrease } from './redux/counter';
function App() {
  const number = useSelector((state:any) => state.counter.number);

  return (
    <div className="App">
          Test App 
          <ReduxComponent number={number}></ReduxComponent>
    </div>
  );
}

export default App;

( ReduxComponent )

import React from 'react';

const ReduxComponent:React.FunctionComponent<any> = ({number,onIncrease,onDecrease}) => {
  
  return (
    <div>
      <h1>{number}</h1>
        <div>
          <button onClick={onIncrease} >+1</button>
          <button onClick={onDecrease} >-1</button>
        </div>
    </div>
    
  );
}

export default ReduxComponent;

 결과

이제 동작하기 위한 Dispatch

import React from 'react';
import './App.css';
import ReduxComponent from './ReduxComponent'
import { useSelector , useDispatch } from 'react-redux';
import { increase, decrease } from './redux/counter';
function App() {
  const number = useSelector((state:any) => state.counter.number);
  const dispatch = useDispatch();
  return (
    <div className="App">
          Test App 
          <ReduxComponent number={number} 
          onIncrease={()=> dispatch(increase)}
          onDecrease={()=> dispatch(decrease)}
          ></ReduxComponent>
    </div>
  );
}

export default App;

실행결과

Error: Actions must be plain objects. Instead, the actual type was: 'function'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.

 

액션은 plain object 여야 한다 -> increase, decrease 액션 디버깅 

 

딱 봐도 객체가 아니라 함수로 리턴 되는 것 처럼 보이네요

       onIncrease={()=> dispatch(increase())}
       onDecrease={()=> dispatch(decrease())}

 

오타였네요 여기서 실행했어야; 

수정하니 정상적으로 잘되네용 ~ hooks 기억 되살리기 끝 다음 포스팅에 작업중인 자동화 플랫폼에 적용 하도록 하겠습니다.

 

컴퓨터 새로 사서 세팅하는 기념 포스팅 ( 지속 업데이트 )

최근에 Mac OS 를 구매했기 때문에 Mac M1 PRO 와 Windows PC 2개로 나눠서 작성 해놓겠습니다.

 

Mac OS PC

Visual Studio Code

1. Git Lens

2. Material Icon Theme

 

Windows PC

Visual Studio Code

1. Better Comments

개발도중 남길수 있는 코멘트들에 대한 가시성이 증대 됩니다.

2. Bracket Pair Colorizer 2

코드 블럭을 가시적으로 보여주게 합니다.

3. DotENV

Node.js에서 많이 사용되는 .env에 대한 기본 세팅입니다.

4. vscode-icons

기본적인 vscode에서 사용되는 icons 테마로 사용중입니다.

 

 

5. SynthWave '84 (디자인 테마 개인 취향)

개인 취향인 디자인으로 Custom CSS and JS Loader 를 사용해서 neon 효과를 줄수 있습니다. 

***command 'synthwave84.enableNeon' not found

금일 새로 설치 해서 세팅하게 되었는데 해당 오류가 발생해서 이것저것 확인해보니 visual studio code 가 1.60 으로 최신버전으로 되어있는데 1.60에서 오류가 나서 동작하지 않았던 이슈가 있었습니다.

VSCodeUserSetup-x64-1.59.1 로 새로 재 설치 후 enable neon dreams 실행시 정상 동작합니다.

visual studio code 1.59 download link - > https://code.visualstudio.com/updates/v1_59

 

 

 

6. Ctrl 로 소스코드 확대

 

Ctrl + Shift + P - > Settings 입력  JSON Default   

검색창 Ctrl 

Editor : Mouse Wheel Zoom 

Zoom the font of the editor when using mouse wheel and holding Ctrl  체크

7. Map 제거

검색창 : Map

Editor > Minimap :Enable 

Controls whether the minimap is shown. - > 체크 해제 

 

 

XShell ( 무료 버전 ) 

1. Ctrl + W : 탭 종료

브라우저는 Ctrl + W 로 탭을  빠르게 종료 하고 다음 작업을 하는데 Xshell 도 같은 작업을 해야하기에 초기에 세팅을 해놓습니다.

 

키 매핑 (P)...

 

새로 만들기(N)....

키 입력후 - 메뉴 - [탭] 닫기 설정

 

2. 마지막 탭 종료시 Xshell 종료 옵션

체크 해제

 

'개인 setting' 카테고리의 다른 글

유용한 사이트 모음 (깍돌이)  (0) 2019.12.24
vim 셋팅  (0) 2016.06.23

안녕하세요 오늘은 네이버 클라우드 플랫폼 이죠 NCP 에 대해서 적어 보려고 합니다.

최근 1년사이에 www.ncloud.com/ 에서도

네이버 클라우드 플랫폼의 최근 추가된 VPC 

VPC(Virtual Private Cloud) 가 출시 되며 기존에 있던 네이버 클라우드 플랫폼에 Classic 환경에서 사용하던

ACG(AccessControlGroup) 은 VM < - > VM 단위의 네트워크 룰을 적용하였다면 VPC가 출시되면서 Subnet 이라는 개념과 함께 Subnet < - > Subnet 의 네트워크 룰을 적용할수 있는 NACL 이 등장하게 되었습니다.

기본적으로  NACL 은 ACG와는 조금 다른 형태를 띄고 있기 때문에 이에 대해서 간단하게 적어 놓도록 하겠습니다.

 

NACL ( Network  Access Control List ) 

네이버 클라우드 플랫폼 환경에서 기존에 Classic에 경우는 ACG를 통하여서 VM 간의 차단룰을 적용했다면 NACL의 경우에는 Subnet 을 통한 차단룰을 적용합니다.  간단하게 설명된 곳이 있어 첨부합니다.

출처 : 코스콤 금융 클라우드 www.koscom.cloud/product/networking/vpc

서버의 접근 제어 서브넷의 접근 제어
허용 (Allow) 허용 및 거부 (Allow/Deny)
상태 저장
(규칙에 관계없이 반환 트래픽이 자동으로 허용됨)
상태 비저장
(반환 트래픽이 규칙에 의해 명시적으로 허용되어야 함)
ACG를 서버의 NIC에 적용 서브넷에 있는 모든 서버에 자동 적용됨

흔히 ACG는 Stateful NACL 은 Stateless라고 표현을 하기도 합니다.  ACG는 따로 포스 중이기 때문에 거기서 설명하도록 하겠습니다. 

NACL 을 적용해 보기 위해서는 일단 네이버클라우드플랫폼에서 VPC와 Subnet을 하나 만들어야합니다.

VPC 생성

Console 접속 후 VPC - VPC Management 에서 [ + VPC 생성 ] 을 클릭하여 아래와 같이 생성합니다.

기본적으로 특수 목적용 사설 IP대역은 위의 범위내에서 지정하도록 되어있기 때문에 저는 10.2.0.0/16 대역으로 지정하였습니다. 이부분에 대해서도 CIDR에 대한 설명이 필요하지만 해당 포스팅에선 따로 설명하지 않겠습니다.

생성이 완료되면 VPC내부에 VM 을 놓기 위한 Subnet을 생성합니다.

 

Subnet 생성 

VPC Management 바로 밑에 있는 Subnet Management 로 들어가서 [ + Subnet 생성 ] 을 선택합니다.

원하는 서브넷 이름을 정하고 방금 생성한 VPC를 선택하도록 합니다.

 

VM 생성

VM 생성은 이제 일반적인 서버 생성을 하신다고 보면되며 2개의 VM 이 생성되었다고 가정하겠습니다.

Send-Server( 10.2.1.55 )    

Recv-Server ( 10.2.1.77 )

 

NACL

NACL 은 기본적으로 VPC가 생성됨에 따라서 vpc-default-network-acl 이 하나 생성되며 아무런 룰도 존재하지않습니다.

NACL 의 Default 룰은 ALLOW 입니다. 그렇기 때문에 기본적인 서버를 생성시에는 ACG룰에 의하여 적용이 됩니다. (ACG는 Default DENY)

위와같이 NACL 의 경우 Default 가 "ALLOW" 이기 때문에 아무런 룰이 없어도 NACL 규칙에 위반되는 트래픽이 발생하지않습니다. 

 

해당 상황에서 

Send-Server( 10.2.1.55 )  ==== > Recv-Server ( 10.2.1.77 ) 에서 10.2.1.77 의 22번 포트차단시 단순하게는 

Case 1

   InBound 10.2.1.77  22 차단

Case 2

   OutBound 10.2.1.77 22 차단

 

2개의 케이스를 도출해낼수 있다고 생각 할수 있는데요  실제로 케이스 1번은 차단되지않습니다. 

처음에 잘못 생각 했던 부분이 Case 1번과 같이 룰을 지정해 놓게 되면

Send-Server 에서 -> Recv-Server 까지는 트래픽이 나가고 ( 인바운드만 차단이니까 ) 다시 들어올 때 10.2.1.77 의 22번 포트로 들어오겠지? 라는 생각에 당연히 막히겠지 라는 잘못된 생각을 가질수 있습니다.

 

간단하게 정리하면

 

NACL

-- inbound 포트 : 목적지 포트 ( VM 으로 들어오는 port )

-- outbound 포트 : 출발지 포트 ( VM 에서 나가는 port )

 

그럼 1번 케이스가 차단이 되어야 하지 않나? 라고 여기서고 생각한다면 OS에 특징을 봐야합니다. 네이버클라우드플랫폼에 있는 Centos 7.8 OS를 기준으로 하게되면  inbound 룰을 차단하게 되면  10.2.1.77 로는 일단 트래픽이 나가고 

다시 들어오는 IP 와 포트의 22을 차단하게 됩니다. 실제로 들어오는 IP는 10.2.1.77 이 들어옵니다. 하지만 Port는 랜덤하게 지정해줘서 들어오게 됩니다.  해당 포트 는 1024 ~ 65535 에서 랜덤으로 주게 되었으며 실제로 우리가 알고 있는 port의 범위는

 

0 ~ 1023 : well-known port번호 해당 영역의 port번호는 UNIX/LINUX에서 root 권한으로만 port를 열 수 있습니다. 예약영역이라고 보면 됩니다. 

1024 ~ 49151번: 등록된 포트 (registered port) 이 영역은 주로 서버 소켓으로 사용하는 영역입니다. 

49152 ~ 65535번 : 동적 포트(dynamic port) 이 영역은 client가 connect(2)시 또는 bind(2)없이 server socket을 생성했어 listen(2)할 경우에 자동으로 할당되는 영역입니다. server socket에서 자동할당하면, client에게 할당된 port번호를 알릴방법이 있어야하기 때문에 서버 소켓은 prot번호를 정합니다.

위와같은 영역이 정해져있습니다. 하지만 우리가 쓰고있는 VM들은 어느 범위 내에서 포트를 할당할시 알수가 있습니다.

바로 port_range 인데요 

네이버 클라우드 플랫폼의 VM인 Centos 7.8 을 기준으로 

vi /proc/sys/net/ipv4/ip_local_port_range

결과

32768   60999

 

해당 범위내에서 포트를 지정해서 다시 돌려주도록 되어있습니다. 제가 Express.js나 Java 서버든 장고를 사용하든 해당 OS설정으로 인해서 해당 포트의 범위를 돌려줍니다.

 

그래서 위의 상황에서 CASE는 아래와같이 변경되어야 합니다.

 

Send-Server( 10.2.1.55 )  ==== > Recv-Server ( 10.2.1.77 ) 에서 10.2.1.77 의 22번 포트 차단시 단순하게는

Case 1

   InBound 10.2.1.77  32768-60999 차단

Case 2

   OutBound 10.2.1.77 22 차단

 

이렇게 되면 1번 케이스에서 55서버는 77서버로 아웃바운드 트래픽을 정상적으로 나가고 77서버는 55서버에게 다시 돌려주기 위해서 10.2.1.77:(32768-60999) 포트를 돌려주게 되며 해당 룰은 차단되어있기 때문에 10.2.1.55 서버에서 NACL 에 의해 막히게 됩니다. 

 

간단하게 NACL 에 대해 살펴 보았습니다. 사실 위에 부분 말고는 처음 NACL 을 접하였을 때 특이사항은 없었던 것 같습니다.  잠깐 신경 안쓰다 보니 다시 잊혀져 가는거 같아서 포스팅을 해놓았습니다.  ㅎㅎ

 

 

감사합니다.

안녕하세요 깍돌이입니다.

 

이제는 FE 쪽 간단한 개발을 위해서 Table 태그를 이용한 TASK 상태를 그려줄 화면을 만들어야하는데요

 

React 에서 해당 table을 만들게 되면 위와같은 warning이 나타납니다.(노랑색도 아니고 빨간색이라 오류로 보고 처리하는게 나을거 같습니다.)

 

 <div>
    <table>
      <colgroup>
        <col width="100px"/>
        <col width="*"/>
        <col width="90px"/>
        <col width="160px"/>
        <col width="120px"/>
        <col width="50px"/>
        <col width="50px"/>
      </colgroup>
      <thead>
          <th>UUID</th>
          <th>TESTNO</th>
          <th>TASK</th>
          <th>TASKSTATUS</th>
          <th>TESTRESULT</th>
          <th>START</th>
          <th>FINISH</th>
      </thead>
      <tbody>
        <tr>
          <td>test-1234-sddf-4321</td>
          <td>1</td>
          <td>할당 및 접속 테스트</td>
          <td>WAIT</td>
          <td>FAIL</td>
          <td>5분전</td>
          <td>2분전</td>
        </tr>
      </tbody>
    </table>
  </div>

위와같이 하였을때 일반 페이지에서는 오류가 발생하지 않지만 React 에서는 해당 태그의 구조는 오류가 발생합니다.

 

validateDOMNesting(...) <th> cannot appear as a child of <thead>

직역을 하자면 th는 thead의 자식으로 나타날수 없다 라는 뜻이 되는데요 간단하게 처리하자면

 

 <div>
    <table>
      <colgroup>
        <col width="100px"/>
        <col width="*"/>
        <col width="90px"/>
        <col width="160px"/>
        <col width="120px"/>
        <col width="50px"/>
        <col width="50px"/>
      </colgroup>
      <thead>

         <tr>
          <th>UUID</th>
          <th>TESTNO</th>
          <th>TASK</th>
          <th>TASKSTATUS</th>
          <th>TESTRESULT</th>
          <th>START</th>
          <th>FINISH</th>

         </tr>
      </thead>
      <tbody>
        <tr>
          <td>test-1234-sddf-4321</td>
          <td>1</td>
          <td>할당 및 접속 테스트</td>
          <td>WAIT</td>
          <td>FAIL</td>
          <td>5분전</td>
          <td>2분전</td>
        </tr>
      </tbody>
    </table>
  </div>

 

th 부분을 tr로 묵어 주면 됩니다.

안녕하세요 깍돌이 입니다 .아파치를 재시작하거나 시작 할때 해당 오류를 만나는 부분들이 꽤있는데요 

아주 단순한 케이스이지만 기록 해놓으려고 작성하게 되었습니다.

 

NCP(NaverCloudPlatform) & Init Script

NCP 클라우드 에서는 VM 을 생성시 Init Script 를 사용하여서 서버가 "운영중"으로 들어오기전에 하고싶은 작업들을 실행할수가 있습니다.  제가 하려고 했던 작업 중 일부분 입니다.

# 아파치 설치 
sudo -s yum -y install httpd
# 아파치 시작 
sudo -s systemctl start httpd
# 아파치 Root 디렉토리 변경
sudo -s sed -i 's/var\/www\/html/home1\/ncloud\/index.html/g' /etc/httpd/conf/httpd.conf
# 아파치 재시작  
sudo -s systemctl restart httpd

작성해놓았지만 동작이 되지않았기에 직접 VM에 들어가서 확인해보니

 

"Job for httpd.service failed because the control process exited with error code"

에러가 발생 중이였습니다. 뒤에 에러 코드에 보면 systemctl status httpd.service 를 보라는 문구가 있죠

 

Systemctl status httpd.service

원래 가장 친절한 에러 문구부터 파악을 해야겠죠

보시면 딱히 어떤 에러가 있는지는 확인이 쉽게 되지는 않게 나타나고 있습니다. 저희는 재시작 혹은 실행 에서 오류가 났기 때문에 해당 status에서 눈여겨 볼 점은 ExecStart 인데요

 

/usr/sbin/httpd 를 통해서 직접 실행을 해봅시다

 

ExecStart - /usr/sbin/httpd

결과 : '/home1/ncloud/index.html' is not a directory , or is not readable 

-> 제가 위에서 변경 하였던 오류였네요 루트 경로를 /var/www/html -> /home1/ncloud 로 했어야 하는데 index.html 까지 써버려서 폴더가 아니기 때문에 아파치의 welcome-list 필터링에서 오류가 발생하였습니다.

 

sudo -s sed -i 's/var\/www\/html/home1\/ncloud/g' /etc/httpd/conf/httpd.conf

 

/home1/ncloud로 변경 후 다시 시도시 (welcome-list에 의해서 index.html 을 찾을 겁니다)

 

정상적으로 나타났네요

 

* 별첨으로 Ubuntu와 Centos에서의 Apache (아파치) 설치 및 루트 디렉토리 변경이 조금씩 다르기 때문에

해당 관련 shell 을 첨부합니다.

 

Ubuntu 16.04-64 Server

#!/bin/bash

sudo -s apt-get update
sudo -s apt-get -y install apache2
# Create Apache New Index Folder
mkdir /home1/ncloud/www

# Change Apache Root Directory
sudo -s sed -i 's/var\/www\/html/home1\/ncloud\/www/g' /etc/apache2/sites-available/000-default.conf

# Change Apache Root Direceotry Access Authority
sudo -s sed -i 's/denied/granted/g' /etc/apache2/apache2.conf

# authority change 
sudo -s chmod -R 775 /home1
sudo -s chmod -R 775 /home1/ncloud
sudo -s chmod -R 775 /home1/ncloud/www
sudo -s chmod -R 775 /home1/ncloud/www/index.html

# Restart Apache
sudo -s /etc/init.d/apache2 restart

 

Centos 7.3-64

#!/bin/bash

# Apache Install
sudo -s yum -y install httpd
# Apache start
sudo -s systemctl start httpd

# Create Apache New Index Folder
mkdir /home1/ncloud/www

# Change Apache Root Directory
sudo -s sed -i 's/var\/www\/html/home1\/ncloud\/www/g' /etc/httpd/conf/httpd.conf

# authority change 
sudo -s chmod -R 775 /home1
sudo -s chmod -R 775 /home1/ncloud
sudo -s chmod -R 775 /home1/ncloud/www
sudo -s chmod -R 775 /home1/ncloud/www/index.html

# Restart Apache
sudo -s systemctl restart httpd

 

감사합니다.

꾸벅 @.@

안녕하세요 깍돌이 입니다.

저번에는 mysql 을 기본적으로 세팅해서 SELECT 문 정도로만 테스트로 해보았었는데요

이번에는 CRUD 작업들을 작성하기 전에 SQL 을 작성할때 흔히 말하는 "날 쿼리" 를 작성하지 않도록 하는 작업부터

해보도록 하려고 합니다.

 

DOCS (node-mysql2) & Prepared Statements 

우선  SQL 인젝션에 대해서 조사를 하다보면 가장 많이 나오는 이야기 들이 "날 쿼리" 를 사용하지 않아야 한다는 점입니다.  예를 들면 

  const sql = `SELECT * FROM ${table} where uuid = ${_uuid}`;

위와같이 작성 하였을 때 날 쿼리라고 하게 됩니다.  _uuid 값은 사용자가 웹에서 입력해서 넘어오는 값이 될수 있습니다. 그렇기 에 입력하는 입력 폼에서 sql injection 이 가능해지는 경우가 됩니다. 

 

또한 해당 입력되는 값에 SQL 의 예약어가 들어오게되면 서버 에러도 유발 시킬수 있습니다. 

 

우선 Prepared Statements 의 경우 왜 인젝션에 방어가 되는가 에 대한 설명은 블로그로 대체 하도록 하겠습니다.

m.blog.naver.com/PostView.nhn?blogId=skinfosec2000&logNo=220482240245&proxyReferer=https:%2F%2Fwww.google.com%2F

 

[SQL인젝션] Prepared Statement를 쓰면 SQL 인젝션 공격이 되지 않는 이유는?

Prepared Statement의 내부 원리 개요 SQL인젝션 취약점에 대한 대응방안으로 Prepared Statement와...

blog.naver.com

SK 인포섹 블로그 라고 되어있는데 보안은 전문가들에게 맡기는게 좋을 것 같습니다. ^^ 

 

그럼이제 Node.js & mysql2 로 해당 과정을 작성하여야 하는데요 위에 잘못된 예시인 "날 쿼리" 를 변경하는 작업을 보겠습니다. 

먼저 제목과 같이 DOCS 도큐먼트라고 하죠 저희가 쓰고 있는 라이브러리는 mysql2 을 쓰고있기 때문에 mysql2 의 도큐먼트를 먼저 찾아 보는게 좋습니다. (docs가 이해가안된다면 한글로 구글링하는 방안으로 ) 도큐먼트를 자주 보다보면 어느샌가 DOCS의 틀? 프레임? 덕분에  검색되는 내용보다 더 이해가 쉽게 되는 경우가 있습니다.

 

Node.js 의 사용모듈의 DOCS를 찾을 때 저는 npm 으로 먼저 확인합니다.

 

1. npm 검색 (mysql2)

 

2. Repository 이동

여기서 HomePage가 DOCS인경우도있고 Repository 가 DOCS인 경우도 있습니다. 둘다 같은 경우도있구요 저는 Repository 로 들어가서 prepared statements에 대해 검색해보았습니다.

 

떡하니 목차가 있네요 이제 해당 DOCS를 보고 위에 있는 "날 쿼리" 를 변경해보도록 하겠습니다.

DOCS 주소입니다.(github.com/sidorares/node-mysql2#using-prepared-statements)

기존 날 쿼리

  const sql = `SELECT * FROM ${table} where uuid = ${_uuid}`;

변경된 쿼리

const sql = `SELECT * FROM ${this.table} WHERE uuid = ?`;

단순 비교하자면 uuid 를 받는 부분이 ? 로 변경되어있습니다. 해당 이유는 ( SK 인포섹 블로그 글 참조 )

 

그리고 해당을 이용하여서 전체 수정시 아래와 같이 변경할수 있습니다.

 const uuid = req.params.id;

    if(uuid!==undefined){
      const sql = `SELECT * FROM ${this.table} WHERE uuid = ?`;

      const connection = mysql.createConnection(this.db_config);
      connection.connect();
  
      connection.execute(sql,[uuid],
        function(err,results,fields){
          if(err) {
            console.error(err);
            res.status(500).json({'message':'DB Result error'});
          }
          else{
            console.log(results);
            res.status(200).json({response:results})
            ;
          }
        })
      connection.end();
    }
    else{
      res.json({message:"Not Exist UUID"});
    }

저희는 express로 DB 에 대한 API 를 작성해야 하기 때문에 위와같이 작성이 되었습니다.

http://{serverIP}/action/uuid 와 같은 형태입니다.   action?uuid= 같은 형태가 아니기때문에 

(? 뒤에 붙는 부분들을 fragment라고 하는데 express에서는 req.query로 작성하면 되지만 위와같은 경우는)

req.params으로 받습니다.  

 

유의사항

기존에는 connection.query -> 변경된 코드를 유심히 보면 connection.exeucte 입니다. 

또한 values를 매핑하기 위한 (? 에 대칭 ) 두번째 파라미터는 [ ] -> 배열의 형태를 가지고있습니다. 이부분 유의하여서 작성하시기 바랍니다. 

 

기본적인 sql Injection 에 대한 SQL prepared statements 였습니다.

 

감사합니다.

 

안녕하세요 깍돌이입니당 ~

저번 포스팅까지는 Mysql 8.0 을 서버에 설치하고  간단하게 SQL 문 등 몇가지 테스트만 해보았는데요

이제는 해당서버에 있는 DB와 연동하기 위한 Manager 서버를 구성하려고 합니다.

 

Node.js 관련 포스팅은 다른글에도 있고 하니 기본적인건 제외 하고 시작 하도록 하겠습니다.

 

Package.json

해당 파일 이미지를 통해 어떠한 부분을 생성하였고 안하였는지 체크 하면 될 것 같습니다.

devDependencies -> npm i -D [모듈명] 으로 설치 한 모듈입니다.

dependencies ->npm i 로 [모듈명] 으로 설치 한 모듈입니다.

** mysql2 의 @types 는  npm i -D types/mysql2 으로 설치합니다.

  "dependencies": {
    "axios": "^0.20.0",
    "cookie-parser": "^1.4.5",
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "helmet": "^4.1.0",
    "moment": "^2.27.0",
    "mysql2": "^2.1.0"
  },
  "devDependencies": {
    "@types/axios": "^0.14.0",
    "@types/cookie-parser": "^1.4.2",
    "@types/cors": "^2.8.7",
    "@types/express": "^4.17.7",
    "@types/moment": "^2.13.0",
    "@types/multer": "^1.4.4",
    "@types/mysql2": "github:types/mysql2",
    "@types/node": "^14.6.2",
    "@types/node-cron": "^2.0.3",
    "@types/serve-favicon": "^2.5.0",
    "pm2": "^4.4.1",
    "typescript": "^4.0.2"
  }

위와같이 설치 되었습니다. ( 작업하다 더 늘어나면 해당 글에 최신화를 할 예정입니다. )

현재 mysql8에는 

위와같이 테스트 데이터를 넣어 놓은 상태입니다.

 

Node.js에서 해당 파일을 mysql2 모듈을 이용해서 CRUD 하는 작업을 간단하게 작성 하도록 하겠습니다.

 

Express & TypeScript

Express.js를 TypeScript로 일단 기본 미들웨어 세팅을 구성하려고 합니다.

 

github.com/lgance/network.git

 

lgance/network

Contribute to lgance/network development by creating an account on GitHub.

github.com

위의 network 로 되어있는 프로젝트가 typescript & Express.js의 Boilerplate code 입니다. 현재는 더이상 사용하거나 추가하지 않기 때문에 위의 프로젝트를 기준으로 작성 하겠습니다.

 

Mysql 연결

기본적인 틀은 위의 github을 통해서 작성을 한 상태로 Mysql 을 연결하는 Router 만 작성하도록 하겠습니다.

 

import * as express from 'express';
import Controller from '../interface/controller.interface'


class ActionStatusController implements Controller {
  public path='/action';
  public pathId='/action/:id';
  public router = express.Router();

  constructor(){
    this.initializeRoutes();
  }
  private initializeRoutes(){
    /** 모든 액션 가져오기  */
    this.router.get(this.path,this.readAllActionStatus);
    /** 특정 액션 가져오기 */
    this.router.get(this.pathId,this.readActionStatus);
    /** 특정 액션 지우기*/
    this.router.delete(this.pathId,this.deleteActionStatus);
    /** 액션 생성 */
    this.router.post(this.pathId,this.createActionStatus);
    /** 액션 변경 */
    this.router.put(this.pathId,this.updateActionStatus);
  }

  private readAllActionStatus = async (req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Read All Action Status');
    res.send(_str);
  }
  private createActionStatus = async(req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Create Action Status');
    res.send(_str);
  }
  private readActionStatus = async (req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Read Action Status');
    res.send(_str);
  }
  private updateActionStatus = async(req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Update Action Status');
    res.send(_str);
  }
  private deleteActionStatus = async(req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Delete Action Status');
    res.send(_str);
  } 

  private _LOG = (req:express.Request):string => {
    const id:string = req.params.id;
    console.log(`Check ID : [${id}]`);
    if(id===undefined){
      return '';
    }
    else{
      return id;
    }
  }

}
export default ActionStatusController;

기본적으로 action Controller를 하나 생성합니다.

 

각각의 기본적인 CRUD를 작성하겠습니다. ( 테스트용 ) 

초반이니까 얼렁 얼렁 작성해서 ㅎㅎ;;

 

일단  http://localhost/action 로 접속 해서 확인해봅시다.

 

정상적으로 나왔으니까 이제 mysql2을 이용하여서 간단한 쿼리로 SELECT 문을 이용하여 가져와 봅니다.

 

mysql 모듈 Import

import * as mysql from 'mysql2';

DB Config 파일 설정 

 private db_config = {
    "host":process.env.DB_HOST,
    "user":process.env.DB_USERNAME,
    "password":process.env.DB_PASSWORD,
    "database":process.env.DB_NAME
  }

ReadAllActionStatus Controller 수정 

 private readAllActionStatus = async (req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Read All Action Status');
    const table = 'status_table';
    const query = `SELECT * FROM ${table}`;

    const connection = mysql.createConnection(this.db_config);
    connection.connect();

    connection.query(query,function(error,results,fields){
      if (error) throw error;
      console.log(results);
  
      res.send(results);
      console.log('connected as id ' + connection.threadId);
    });
    connection.end();
   
  }

 

 

결과는 ? 

 

네 정상적으로 연결되어 값을 가져옴을 확인하였습니다.  하지만 여러가지 문제가 있습니다.

 

1. query Callback 

connection.query 의 Callback 으로 결과를 받은 후에 res.send 를 해주기 때문에 Client한테 지연을 줄수 있습니다. (좋은방법 X)

 

2. SQL Injection 

sql injection 에 당하기 딱 좋은 방법이죠 위의 코드에선 안당하겠지만 (너무단순한 SQL 문이라)

이부분을 유의하여서 앞으로 작성하도록 하겠습니다.

 

감사합니다.

 

Thanks for always

 

 

 

 

 

 

 

 

안녕하세요 기본 세팅 및 정리  [2] 를 작성하다가 컴퓨터를 꺼버리게 되어서 작업 내용이 날아갔네요 ㅜㅜ

 

다시 기억나는대로 작성 해야겠습니다.  관련 내용은 ( DB Tool 인 Dbeaver 와  외부접속을 위한 Mysql 8.0 서버 세팅 등..)

 

이번에는 서버 세팅후에 이것저것 하나씩 찾아 보았던 부분들을 하나의 페이지로 작성하려고 합니다. 현재 DB 를 학부생 시절 이후로 아예 지식이 전무한 상태에서 DB관련 작업 및 세팅을 해야되서 구글의 힘을 빌리고 있습니다. ㅜ 

 

간단 시연 후에는 전반적으로 DB자체에 대한 공부를 진행 하여 포스팅 하도록 하겠습니다.

 

Date ( Mysql Date 현재 시간 )

Test Automation Platform 을 제작하는 부분에 있어서 모든 액션의 단계에서는 언제 실행되었는지에 대한 값이 필요하기 때문에 Date라는 컬럼이 꼭 필요하여야 했습니다. 또한 절대 없으면 안되는 값이 였구요  그래서 이 값을 DB에서 제공하는 현재 시간 값을 자동으로 넣을지 혹은 node.js에서 new Date를 찍어서 DB에 넣어 줄지에 대한 선택이 필요 하였고 근처 지인 개발자들에게 질문 했을 시 답은 DB에 있는 기본 날짜를 사용하라 였습니다.  단순하게 보자면 DB에서 new Date를 기본값으로 찍히는게 Node.js 에서 매번 insert 할 때 마다 new Date를 찍는거보다 훨씬 빠르고 부하가 적기 때문입니다.

 

그래서 생성된 Date컬럼에 기본값으로 넣어 주기 위해서 아래의 SQL문을 작성 하였습니다.

ALTER TABLE [테이블명] MODIFY COLUMN [컬럼명] datetime DEFAULT current_timestamp NOT NULL;
ALTER TABLE status_table MODIFY COLUMN `date` datetime DEFAULT current_timestamp NOT NULL;
ALTER TABLE [테이블명] MODIFY COLUMN [컬럼명] datetime DEFAULT now() NOT NULL;
ALTER TABLE status_table MODIFY COLUMN `date` datetime DEFAULT now() NOT NULL;

 

저는 현재 Mysql 8.0 버전을 사용하고 있으며 위의 2개 SQL문 이 되는 점을 확인 하였습니다.

alter를 이용하여 date 컬럼에 기본 값으로 now() 혹은 current_timestamp 을 사용하며 해당 컬럼은 NOT NULL 으로 설정 합니다.

 

Insert 문을 이용하여서 테스트시 

정상적으로 기본 날짜로 들어가는 점이 확인되었습니다.

 

AutoIncrement  초기화

NO 컬럼을 이용하여서 DB의 autoincrement 을 사용하지만 개발 중 테스트 단계에서는 이부분은 초기화 할 일이 있었습니다. 당연히 이후로는 없어야 할 것 이구요

NO 컬럼에 autoincrement가 들어가있습니다. 테스트 중이니 추후에 는 꼭 초기화가 필요합니다.

1- 초기화 를 위해 아래의 SQL문을 사용 합니다. 

ALTER TABLE [테이블 명] AUTO_INCREMENT = [시작 값]
ALTER TABLE status_table AUTO_INCREMENT=0

 ALTER TABLE [테이블명] AUTO_INCREMENT=[시작값] 인데요

 

해당 SQL문 전에

위와같은 데이터가 있은 후에 다시 INSERT를 하게되면

보시는 것처럼 AUTO_INCREMENT 값이 초기화가 되어있지 않습니다.  이를 동작하게 하기위해선 시작하는 AUTO_INCREMENT 값보다 더 큰 값이 있으면 안되기 때문인데요 해당 데이터를 전부 날려보고 다시 작업합니다.

 

DELETE FROM [Table명]
DELETE FROM status_table

아직은 테스트 단계니 날려보고 다시 INSERT를 하게되면

0부터 시작하게 되어 1이 표시됨을 확인 할수 있습니다.

 

 

 

 

 

 

 

 

 

전 포스팅인 세팅 [1] 에서 세팅이 끝났다면 이제 사용해야할 툴 을 다운로드 하여야 합니다.

 

필요하다면 generated 된 비밀번호를 변경하여야 합니다.

 

비밀번호 변경

mysql -u root -p 명령어로 로그인 시 비밀번호 ( generated - /var/log/mysqld.log ) 를 입력 후 해당 mysql은 8.0 버전 이기 때문에 로그인 후 mysql 선택시

 

mysql> use mysql;
No connection. Trying to reconnect...
Connection id:    11
Current database: *** NONE ***

ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
mysql> 

 

와 같이 비밀번호를 변경해야 하는 문구가 발생합니다. 그래서 관련 내용을 찾기 위해 구글링을 해보았습니다.

1.  SET PASSWORD 

mysql> SET PASSWORD = PASSWORD('ppap1234!@');
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'PASSWORD('ppap1234!@')' at line 1

정상적으로 되지않습니다.

2. ALTER 

mysql> ALTER USER'root'@'%' IDENTIFIED BY 'Ppap1234!@';
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

정상적으로 되지않았습니다.

 

해결

mysql> set password='Testpw12#$';
Query OK, 0 rows affected (0.01 sec)

FLUSH PRIVILEGES;

 

이제 변경도 완료 했기 때문에 아래와 같이 정상적으로 변경됨을 기대합니다.

mysql> use mysql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> 

위의 상태를 만들어 놓은 후 

 

DBeaver 

네 DBMS 관리 UI 툴로 제가 선택하게 된 툴입니다.

해당 설치를 위해서 https://dbeaver.io/ 로 이동 합니다.   메인 화면에서 Download를 선택 후  Windows Installer 

 

설치 후 실행 시 Mysql 선택

 

여러가지 세팅을 완료하고 접속을 하려고하면 "is not allowed to connect to this MySQL Server"  메시지가 발견된다.

포스팅 1에서 말씀 드렸듯이 NCP(NaverCloudPlatform)의 VM 을 사용하였습니다.  접속이 안되는 메시지 이기 때문에 1차적으로 ACG를 확인하여야 합니다. ( 공인 IP는 달려있는 상태입니다. ) 

 

테스트 용 및 Dev , Beta 환경이기 때문에 으로 일단은 0.0.0.0/0 의 3306 포트를 열어 놓았습니다. ( 위의 환경은 민간 Classic 환경입니다. ) -> 만약에 Prod 환경이라면 3306를 열지 않아도 BE를 통해서 DB를 조작하기때문에 상관이 없을 수 있습니다. 

--> ? 하지만 결과는 동작하지 않습니다. 

서버도 잘 떠있는데 말이죠 ... 여기서 이제 확인해야 할 것은 mysql 자체의 방화벽 설정입니다.

mysql 자체에서 허용을 해줘야 하는 부분인데 아무리 테스트여도 root로 열어주긴 싫기 때문에 ID를 새로 생성하였습니다.

// nstpmanager 계정 생성 
CREATE USER nstpmanager@'%' IDENTIFIED BY '비밀번호';

// nstpmanager 계정에 모든 IP허용
GRANT ALL ON *.* TO nstpmanager@'%';

 

그리고 DBeaver 에서 재시도

이부분은 간단합니다. 해당 Driver properties 에서 allowPublicKeyRetrieval 을 YES 로 허용해주면 됩니다.

 

 

완료 

 

 

** caching_sha2_password 오류에 대해서

아래의 SQL 구문을 통해서 DBeaver 접속을 해결하였지만 의문이 들긴 합니다.최신버전에서 sha2 방식을 사용한다면 해당 플러그인을 툴에서 지원하던지 해야되는데 접속을 위해서 DBMS에서 기존방식을 채택한다라 이부분도 나중에 확인해야 할 과제겠네요

 

 

안녕하세요 깍돌이입니다.

 

저ㅓㅓㅓㅓㅓㅇ말 오랜만에 글을 작성하는 거 같습니다. ㅜ

 

제품 릴리즈 배포하느라고 너무 바빴습니다. ( 변명 )  

 

사실 데모 시연을 하기 위해서 DB서버를 하나 세팅해놓아야 하는데 하는 일이 생겨서 남겨 놓으려고 합니다. 

 

OS는 Centos 7.8 

DBMS는 Mysql 8.0.xx버전을 사용할 생각입니다.

개발 플랫폼 Node.js를 사용합니다.

 

 

OS

Centos 7.8 은 설치가 되어있다는 가정하에 작성하겠습니다. 저는 NCP(NaverCloudPlatform)에 있는 Centos 7.8을 사용하였습니다.

 

DBMS

Mysql을 설치하기 위해서는 사이트를 접속해야 되니 아래의 사이트로 접속합시다.

https://www.mysql.com/downloads/

 

MySQL :: MySQL Downloads

Contact MySQL  |  Login  |  Register The world's most popular open source database MySQL.com Downloads Documentation Developer Zone MySQL Enterprise Edition includes the most comprehensive set of advanced features and management tools for MySQL. MySQL

www.mysql.com

Enterprise는 기업용이기 때문에 유료 형태로 제공이 됩니다. 우리는 무료 버전을 써야 하기 때문에

밑에 있는 Community Download를 클릭합시다.

 

Centos의 경우 YUM Repo를 사용하기 때문에

 

 

MySQL Yum Repository를 선택합니다.

 

그리고 Centos 7버 전이기 때문에 7 버전에서 Download 버튼을 클릭합니다.

 

 

Download버튼을 누르면 ~ 로그인을 해야 된다 뭐 이런 이야기가 나오는데요

 

하실 필요는 없으시고  No thanks, just start my download -> 우클릭 -> 링크 주소 복사 (E)

하고 

 

나의 VM으로 접속하셔서

 

 

wget [복사한 링크  붙여 넣기]  후 Enter 시 위와 같이 mysql repo 작업을 하는 rpm 이 다운로드되고

 

rpm -ivh mysql80-community-release-el7-3.noarch.rpm 

을 입력하여 레포를 설정합시다.

 

작업 후 확인을 위해서 

cat /etc/yum.repos.d/mysql-community.repo를 확인해 봅시다

 

 

 

우리의 Centos에 mysql 8.0을 깔기 위한 repository 작업이 끝났습니다.

yum -y install mysql-server로  해당 레포를 가지고 설치를 시작해봅시다.

 

설치 완료 후 mysql이라고 명령어를 검색하게 되면

 

Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock ' (2) 에러가 발생합니다.

mysql 이 시작되기 않았기 때문인데요

 

아래의 명령어로 실행 , 활성화, 상태를 확인해봅시다

 

sudo systemctl start mysqld
sudo systemctl enable mysqld
sudo systemctl status mysqld

 

 

mysql_secure_installation라는 걸 통해서 보안설정을 하라는 다른 포스팅도 있고 그런데요 저는 8.0을 설치했기 때문에 따로 할 필요는 없어 보입니다.

 

 

 

mysqld -uroot -p

 

입력하시게 되면 root 비밀번호를 입력하여야 하는데 

?? 설정한 적이 없기 때문에 비밀번호를 알 수가 없습니다.

 

그러기 때문에 대부분 비밀번호 입력 없이 엔터를 치시면 접속이 된다고 하였지만

 

ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)

 

-> 저는 접속이 되지 않았습니다. ㅜ

 

일단 접속을 하기 위해서

 

vi /etc/my.cnf 파일에

 

skip-grant-tables를 입력하고 저장합니다.

 

 

그리고 systemctl restart mysqld

mysql -u root -p 비밀번호 무시하고 엔터 

 

 

접속 성공!!

 

 

------> 위의 과정은 모든 게 잘못된 과정이었습니다. 

 

Repo 까진 정상이었고 설치 시에 mysql-server가 아니라 

mysql-community-server로 설치하여야 합니다.

(5.6 이후로는 무료 버전에 community 가 붙는다고 합니다. )

 

yum -y install mysql-server              ----- > ( X ) 

yum -y install mysql-community-server ---->( O ) 

 

설치 완료 후

 

systemctl start mysqld

mysql -u root -p 시 비밀번호는

 

vi /var/log/mysqld.log

로그에 들어가면 password 가 generator 되어있습니다.

 

해당 비밀번호를 가지고 접속하시면 됩니다.

 

진짜 접속 성공!!

 

 

 

+ Recent posts