독까의 이야기

일반적으로 윈도우 데스크탑, SERVER OS 를 이용하는 사용자들은 MS 에서 매달 제공하는 윈도우 업데이트를 진행한다.

MS 서버에 등록 된 업데이트 파일을 다운로드 및 설치를 하게 되는데, 해당 기능을 업데이트 전용 서버 구축을 통해 내부적으로도 이용이 가능하다. 

윈도우 서버에서 제공하는 WSUS (Windows Server Update Services) 구축 및 테스트를 진행한다. 


구성 서버 : WSUS 기반 서버 (W1) / 클라이언트 서버 1 (C1) / 클라이언트 서버 2 (C2)

OS : Windows 2012 R2 STD


# 1 서비스 설치 


W1 서버 관리자에서 역할 및 기능 추가



Default 로 쭉쭉 간다. 


컨텐츠 위치 선택에서 임의 지정한 경로를 입력한다. 



IIS 설치 관련도 그냥 Default 로 간다. 



설치 완료 후, Windows Server Update Services 를 실행하면, 아래와 같이 나오는데 그냥 실행 하면 된다. 

설치 완료까지 시간이 소요 될 수 있다. 



설치 마법사 나오는데 또 Default 로 간다. 



업스트림 서버에 연결 부분에서 시간이 많이 소요되는 것 같다. 서버나 네트워크 환경에 따라 달라질 것 같다. 



언어 선택도 그냥 Default 로 간다. 



업데이트 제품 선택이 나오는데 Default 로 정해진 옵션을 사용해도 되고, 원하는 항목을 추가해도 된다.

선택 항목에 따라 다운로드 해야하는 파일이 증가한다. 

테스트 목적이기 때문에 Windows Server 2012 R2 항목만 체크했다. 

업데이트 제품은 나중에도 추가, 제거 가능하다. 



업데잍 등급 선택이 나오는데 이것도 나중에 추가, 제거 가능하니깐 Default 로 간다. 



동기화 일정도 본인이 편한대로 하면 된다. 대부분 자동을 선택하겠지만. 



마무리 단계에서 초기 동기화 시작을 체크하고 다음을 진행한다. 




업데이트 서비스 실행하면 이런 화면을 볼 수 있다. 추가 구성은 클라이언트 세팅 후 한다. 





# 2 클라이언트 환경 설정


C1, C2 에서는 W1 에서 업데이트 항목을 받아오는 설정을 진행해야 한다. 


실행에서 gpedit.msc 입력 



컴퓨터 구성 - 관리 템플릿 - Windows 구성 요소 - Windows 업데이트 - 인트라넷 Microsoft 업데이트 서비스 위치 지정 선택



상태 : 사용

옵션에는 W1 의 주소값을 입력한다. (ex: http://W1:8530) / 내 경우에는 hosts 파일에 아이피 호스트명을 직접 등록했기 때문에 예시와 같이 설정이 가능하다. 

일반적으로는 http://192.168.1.5:8530 으로 입력한다. 



설정 했으면 실행에서 gpupdate /force 입력해서 적용해준다. 



레지스트리에서 확인하면 아래와 같다. 


[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate]




업데이트 실행하면 "시스템 관리자가 관리합니다." 로 변경되어 있음을 확인 할 수 있다. 

하단 "Windows 업데이트에서 온라인 업데이트 확인" 누르면 기존처럼 MS 업데이트 서버에서 다운로드 되니깐 본인 편한대로 하면 된다. 





# 3 업데이트 항목 호출


W1 으로 돌아와서 컴퓨터 카테고리를 누르면 C1, C2 가 추가 된 것을 확인 할 수 있다. 

그룹에 할당되지 않았기 때문에 "할당되지 않은 컴퓨터 그룹"에 자동으로 들어간다. 

표시되는 정보는 클라이언트의 정보를 그대로 갖고 오기 때문에 수정은 안 된다. 


테스트용 그룹을 만들어서 그룹 이동을 진행한다. 



그룹 구성원 변경 확인 되었다. 



업데이트 카테고리- 모든 업데이트 - 전체 선택 - 우클릭 - 승인 - WSUS_TEST 그룹 선택 - 설치 승인




C1 을 선택 후 상태 보고서를 실행하면 업데이트 현황에 대해 확인 가능하다. 

OS 설치 후 업데이트를 진행하지 않았기 때문에 설치 내역을 확인 할수가 없다. 



W1 을 통해 다운로드 및 설치 가능한 업데이트 항목을 확인 할 수 있다. 



C1, C2 에서 업데이트 확인을 다시 누르면 설치 가능 업데이트 항목이 출력 된다. 




WSUS 를 통해서 업데이트하면 수동으로 업데이트 승인 및 선택, 설치를 해야하기 때문에 번거로울 수 있으나, 특정 업데이트만 설치를 하고자 할 경우에는 유용 할 수 있다. (WSUS 에 해당 업데이트 파일이 존재해야 함)

물론 특정 업데이트도 MS 업데이트 카탈로그에서 검색하면 다운로드 받을 수 있다. (https://www.catalog.update.microsoft.com/Home.aspx)



MS 를 통한 보안 업데이트시에는 아래와 같이 자동으로 필요한 항목들에 대해서만 정보를 갖고 온다. 



그러므로 가급적이면 MS 업데이트 사용을 권장한다. 


폐쇄망 네트워크로 인해 외부 접근이 용이하지 않은 업체에서나 WSUS 기능을 이용하는 것이지, 일반 네트워크 환경은 MS 업데이트를 이용하는 것이 낫다.

굳이 불편을 감수할 필요는 없을 것 같다. 


간혹 MS 업데이트 서비스 비정상 작동으로 80072EE2 오류 코드 출력될 때 이용하는 것도 좋은 방법이다. 

문제 1 (난이도 : 中)  
N 줄인 삼각형을 출력한다. 단, 사용자로 부터 임의의 N 을 입력 받는다. 아래는 N = 3 일 때의 출력 예시 이다.  

*    
**  
***

# 접근 1.
다른 강좌나 게시글을 보면, 출력 결과물에 대한 설명이 부족해서 이해가 잘 안갔다. 
수학을 가르치지만 프로그래밍은 다룬적 없는 와이프에게 수학적으로 설명을 해달라고 부탁했다.
와이프는 수식을 보고 수기로 풀어 보더니 금방 이해가 되었다고 했다. 
아이들에게 가르치는 방식으로 설명을 요청했다. 
별(*) 에 대한 개념을 수로 바꾸어서 표현하니 이해가 쉬웠다. 

#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = 1; j <= i; j++)
                     printf("*");
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = 1; j <= i; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
몇 줄 짜리 삼각형? : 5
i = 1 :  *
i = 2 :  **
i = 3 :  ***
i = 4 :  ****
i = 5 :  *****
최종 i 값 = 6
몇 줄 짜리 삼각형? : 5
i = 1 :  1
i = 2 :  12
i = 3 :  123
i = 4 :  1234
i = 5 :  12345
최종 i 값 = 6


#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = input; i >= 1; i--)
       {
              printf("i = %d :  ", i);
              for (j = 1; j <= i; j++)
                     printf("*");
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = input; i >= 1; i--)
       {
              printf("i = %d :  ", i);
              for (j = 1; j <= i; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
몇 줄 짜리 삼각형? : 5
i = 5 :  *****
i = 4 :  ****
i = 3 :  ***
i = 2 :  **
i = 1 :  *
최종 i 값 = 0
몇 줄 짜리 삼각형? : 5
i = 5 :  12345
i = 4 :  1234
i = 3 :  123
i = 2 :  12
i = 1 :  1
최종 i 값 = 0
위 두 수식의 차이점은 
for (i = 1; i <= input; i++)    /    for (i = input; i >= 1; i--)
외에는 없다. 
위에서 아래로 늘어나는 삼각형을 역순으로 만들려면 적용 방식을 역순으로 하면 된다. 

이렇게 설명을 해주었으면 좋았을 텐데, 강좌나 게시들에서는 "문제, 답" 이렇게만 적어놔서 조금 아쉽다. 


# 접근 2.
우측 정렬 된 삼각형을 만들어보자. 

#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = input; i >= 1; i--)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j <= i; j++)
                     printf("*");
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = input; i >= 1; i--)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j <= i; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = input; i >= 1; i--)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf("%d", j);
              for (j = 1; j <= i; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
몇 줄 짜리 삼각형? : 5
i = 5 :  *****
i = 4 :   ****
i = 3 :    ***
i = 2 :     **
i = 1 :      *
최종 i 값 = 0
몇 줄 짜리 삼각형? : 5
i = 5 :  12345
i = 4 :   1234
i = 3 :    123
i = 2 :     12
i = 1 :      1
최종 i 값 = 0
몇 줄 짜리 삼각형? : 5
i = 5 :  12345
i = 4 :  51234
i = 3 :  54123
i = 2 :  54312
i = 1 :  54321
최종 i 값 = 0


#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j <= i; j++)
                     printf("*");
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j <= i; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf("%d", j);
              for (j = 1; j <= i; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
몇 줄 짜리 삼각형? : 5
i = 1 :      *
i = 2 :     **
i = 3 :    ***
i = 4 :   ****
i = 5 :  *****
최종 i 값 = 6
몇 줄 짜리 삼각형? : 5
i = 1 :      1
i = 2 :     12
i = 3 :    123
i = 4 :   1234
i = 5 :  12345
최종 i 값 = 6
몇 줄 짜리 삼각형? : 5
i = 1 :  54321
i = 2 :  54312
i = 3 :  54123
i = 4 :  51234
i = 5 :  12345
최종 i 값 = 6


# 접근 3.
피라미드형 삼각형을 만들어보자.

#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j < i * 2; j++)
                     printf("*");
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j < i * 2; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf("%d", j);
              for (j = 1; j < i * 2; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
몇 줄 짜리 삼각형? : 5
i = 1 :      *
i = 2 :     ***
i = 3 :    *****
i = 4 :   *******
i = 5 :  *********
최종 i 값 = 6
몇 줄 짜리 삼각형? : 5
i = 1 :      1
i = 2 :     123
i = 3 :    12345
i = 4 :   1234567
i = 5 :  123456789
최종 i 값 = 6
몇 줄 짜리 삼각형? : 5
i = 1 :  54321
i = 2 :  543123
i = 3 :  5412345
i = 4 :  51234567
i = 5 :  123456789
최종 i 값 = 6

#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = input; i >= 1; i--)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j < i * 2; j++)
                     printf("*");
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = input; i >= 1; i--)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j < i * 2; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
#include <stdio.h>
int main() {
       int i, j, input;
       printf("몇 줄 짜리 삼각형? : ");
       scanf_s("%d", &input);
       for (i = input; i >= 1; i--)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf("%d", j);
              for (j = 1; j < i * 2; j++)
                     printf("%d", j);
              printf("\n");
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
몇 줄 짜리 삼각형? : 5
i = 5 :  *********
i = 4 :   *******
i = 3 :    *****
i = 2 :     ***
i = 1 :      *
최종 i 값 = 0
몇 줄 짜리 삼각형? : 5
i = 5 :  123456789
i = 4 :   1234567
i = 3 :    12345
i = 2 :     123
i = 1 :      1
최종 i 값 = 0
몇 줄 짜리 삼각형? : 5
i = 5 :  123456789
i = 4 :  51234567
i = 3 :  5412345
i = 2 :  543123
i = 1 :  54321
최종 i 값 = 0


# 접근 4.
다이아몬드 형태를 만들어 보자. 
정방향과 역방향을 합치고 중복되는 정가운데를 상쇄하면 될 것 같다. 

int main() {
       int i, j, input;
       printf("가장 긴 행의 수 입력 : ");
       scanf_s("%d", &input);
       for (i = 1; i <= input; i++)
       {
              printf("i = %d :  ", i);
              for (j = input; j > i; j--)
                     printf(" ");
              for (j = 1; j < i * 2; j++)
                     printf("*");
              printf("\n");
              if (i == input) {
                     for (i = input - 1; i >= 1; i--)
                     {
                           printf("i = %d :  ", i);
                           for (j = input; j > i; j--)
                                  printf(" ");
                           for (j = 1; j < i * 2; j++)
                                  printf("*");
                           printf("\n");
                     }
                     break; // 조건 완료시 중지를 하지 않으면 무한루프 발생
              }
       }
       printf("최종 i 값 = %d", i);
       return 0;
}
가장 긴 행의 수 입력 : 5
i = 1 :      *
i = 2 :     ***
i = 3 :    *****
i = 4 :   *******
i = 5 :  *********
i = 4 :   *******
i = 3 :    *****
i = 2 :     ***
i = 1 :      *
최종 i 값 = 0


MS-SQL 2008 ENT 이상 버전에서 지원되는 TDE 기능에 대한 테스트를 진행 한다. 
STD 에서도 복원은 가능하나, DB 는 사용 할 수 없다. 

OS : Windows 2016
DBMS : MS-SQL 2014 ENT

1. DB 생성
DB 명 : gunnm 
USE master;
CREATE DATABASE [gunnm]
ON
( NAME = gunnm, FILENAME = 'D:\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\gunnm.mdf')
LOG ON
( NAME = gunnm_log, FILENAME = 'D:\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\gunnm_log.ldf')
GO




2. 암호화 진행

마스터 키 생성
USE master
GO
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'PassWord!2#'
GO

마스터 키로 보호 된 인증서 생성
CREATE CERTIFICATE gunnm_cert WITH SUBJECT = 'gunnm_cert'
GO

마스터 키 백업
USE master
BACKUP SERVICE MASTER KEY TO FILE = 'H:\service_master.key' ENCRYPTION BY PASSWORD = 'Service_PW@1119'
GO
BACKUP MASTER KEY TO FILE = 'H:\db_master.key' ENCRYPTION BY PASSWORD = 'DB_PW#1119'
GO

DB 암호화 키 생성
USE gunnm
GO
CREATE DATABASE ENCRYPTION KEY
WITH ALGORITHM = AES_256
ENCRYPTION BY SERVER CERTIFICATE gunnm_cert 
GO

실행 메시지 
경고: 데이터베이스 암호화 키를 암호화하는 데 사용된 인증서가 백업되지 않았습니다. 인증서와 인증서에 연결된 개인 키를 즉시 백업해야 합니다. 인증서를 사용할 수 없게 되거나 다른 서버에서 데이터베이스를 복원하거나 연결해야 할 경우 인증서와 개인 키의 백업본이 있어야 합니다. 그렇지 않으면 데이터베이스를 열 수 없습니다.

DB 암호화 적용
USE gunnm
GO
ALTER DATABASE gunnm
SET ENCRYPTION ON
GO

DB 암호화 키 백업
USE master
BACKUP CERTIFICATE gunnm_cert TO FILE = 'H:\gunnm_cert.cer' WITH PRIVATE KEY ( FILE = 'H:\gunnm_cert.pvk' , ENCRYPTION BY PASSWORD = 'gunnm_PW$1119' )
GO

DB 백업
BACKUP DATABASE [gunnm] TO  DISK = 'H:\gunnm.bak';
GO



3. 대상 서버에서 DB 복원 진행

원본 서버에서 생성 된 DB 인증서 파일 (.cer / .pvk) 및 DB 백업 파일 (.bak) 을 복사한다. 

마스터 키 생성 : 원본 서버와 동일한 패스워드를 입력할 필요는 없다. 
USE master
GO
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'PassWord1@3'
GO
CREATE CERTIFICATE gunnm_cert WITH SUBJECT = 'DB_Enc'
GO

원본 서버 인증서 파일을 통한 인증서 생성 및 DB 복원
CREATE CERTIFICATE gunnm_cert
  FROM FILE = 'H:\gunnm_cert.cer'
  WITH PRIVATE KEY ( 
    FILE = 'H:\gunnm_cert.pvk',
 DECRYPTION BY PASSWORD = 'gunnm_PW$1119'
  );

실행 메시지 : 
메시지 15232, 수준 16, 상태 1, 줄 2
이름이 'gunnm_cert'인 인증서가 이미 있거나 이 인증서가 데이터베이스에 이미 추가되었습니다.
테스트이므로 서버간 이동을 하지 않고, 동일 서버에서 진행되어 발생 된 오류로 추정

-- 등록 인증서 제거
USE master; 
DROP CERTIFICATE gunnm_cert;


CREATE CERTIFICATE gunnm_cert
  FROM FILE = 'H:\gunnm_cert.cer'
  WITH PRIVATE KEY ( 
    FILE = 'H:\gunnm_cert.pvk',
 DECRYPTION BY PASSWORD = 'gunnm_PW$1119'
  );

RESTORE DATABASE [gunnm]
  FROM DISK = 'H:\gunnm.bak'
  WITH MOVE 'gunnm' TO 'D:\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\gunnm.mdf',
       MOVE 'gunnm_log' TO 'D:\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\gunnm_log.ldf';
 



4. TDE 모니터링 스크립트
SELECT DB_NAME(database_id) AS DatabaseName, encryption_state,
encryption_state_desc =
CASE encryption_state
         WHEN '0'  THEN  'No database encryption key present, no encryption'
         WHEN '1'  THEN  'Unencrypted'
         WHEN '2'  THEN  'Encryption in progress'
         WHEN '3'  THEN  'Encrypted'
         WHEN '4'  THEN  'Key change in progress'
         WHEN '5'  THEN  'Decryption in progress'
         WHEN '6'  THEN  'Protection change in progress (The certificate or asymmetric key that is encrypting the database encryption key is being changed.)'
         ELSE 'No Status'
         END,
percent_complete,encryptor_thumbprint, encryptor_type  FROM sys.dm_database_encryption_keys 


USE master
GO
SELECT * FROM sys.certificates

-- encryption_state = 5 is encrypted
SELECT * FROM sys.dm_database_encryption_keys
WHERE encryption_state = 3; 



5. 암호화 제거

DB 암호화 비활성화
USE gunnm
ALTER DATABASE gunnm SET ENCRYPTION OFF
GO

DB 암호화 키 제거
USE gunnm
GO
DROP DATABASE ENCRYPTION KEY  

마스터 키 및 인증서 제거
-- 마스터 키 제거
USE master;
drop master key;
GO

-- 등록 인증서 제거
USE master; 
DROP CERTIFICATE gunnm_cert;
GO