리눅스에서의 가상화 솔루션의 종류와 특성 (1)

기준 환경:

Ubuntu 20.04.1 LTS (WSL2 by Windows 10 x64)

이 글은 가상화에 대해 간략히 소개한다. 다음 글에서 리눅스에서의 가상화에 대해 조금 더 깊게 다룰 예정이다.


가상화란

물리 자원을 추상화하는 방법 중 하나로, 하나의 물리 자원을 여러 개의 논리적 자원으로 나누거나, 여러 물리 자원을 하나의 논리적 자원으로 다루는 것을 말한다.

  • 각 자원이 서로에게 영향을 끼치지 못하므로, 장애 전파가 되지 않는다.

  • 논리적인 자원을 유지하면서, 물리적인 자원을 교체할 수 있다.

  • 물리적 자원간의 약간의 차이를 무시하기 위해 일반 사양으로 논리적 자원을 구성할 수 있다.


가상화의 원리와 Linux에서의 가상화

기본적으로 가상화는 HW에서 지원해줘야 한다. 실제 CPU에 Guest OS를 실행했을 때, Guest OS가 제어권을 Host OS로 반환할 지 알 수 없기 때문이다. 따라서, HW는 Host OS가 관리할 수 있는 방식의 규칙과 제약을 제공한다. 출처

가상화 방식은 크게 Type 1 (= Full Virtualization), Type 2(= Para Virtualization) 가 있는데, 리눅스에서는 KVM, XEN이 주요 가상화 솔루션이다.

  • Type 1의 경우 OS 레벨 아래에 Virtual Machine Monitor(Hypervisor)가 HW Layer 사이에 낀 Layer로 하드웨어를 가상화한다. KVM이 Type 1이며, Windows Hyper-V도 Type 1이다.

  • Type 2의 경우 Host OS와 Guest OS 사이에 Hypervisor가 위치한다. VirtualBox, VMWare 등이 Type 2이다. Mac의 경우 Parallels가 Type 2이다.

  • Type 2의 경우 상대적으로 높은 성능을 제공한다고 알려져 있다.

  • 다만 Type 2 방식은 기존 OS에 수정을 해서 Guest OS를 만들어야 한다는 단점이 있다.

  • 리눅스는 Type 2 방식의 Guest OS를 잘 지원한다.

  • Docker의 경우 컨테이너 기술이 커널 위에서 지원되므로 Hypervisor가 필요 없으나, Windows 및 Mac에서는 Linux Kernel이 필요하므로 리눅스 OS를 가상화로 띄워야 하므로 Hypervisor가 필요하다.

리눅스 - ssh/sshd의 보안에 대해 (2/2)

이 글은 1편에 이어 SSHD의 보안 설정을 다룬다. SSHD의 설정 파일은 /etc/ssh/sshd_config 인데 아래의 내용은 해당 파일의 설정을 다룬다. 설정을 변경한 경우 sudo systemctl restart sshd로 SSHD를 재시작해줘야 한다. (WSL의 경우 systemctl이 지원되지 않는데, 단순히 sudo service ssh --full-restart를 실행하면 된다.)

기준 환경:

Ubuntu 20.04.1 LTS (WSL2 by Windows 10 x64)
OpenSSH_8.2p1 Ubuntu-4ubuntu0.2, OpenSSL 1.1.1f 31 Mar 2020


SSH 비밀번호 시도 횟수 제한

기본 설정은 사용자의 비밀번호 입력 오류가 아무리 많아도 접속을 끊지 않는데, 이 횟수를 제한하고 횟수를 넘으면 접속을 끊을 수 (Too many authentication failures) 있다. #MaxAuthTries 6 으로 적혀있는 주석(#)을 제거하면 된다.


SSH 포트 변경

#Port 22 에서 Port 2222 등으로 변경해 포트를 변경할 수 있다. 변경된 포트로의 접속은 ssh -p2222 sb@localhost 로 포트를 옵션으로 제공해 접속할 수 있다.


프로토콜 버전 2 사용 (1 사용 금지)

Protocol 2 라고 명시하므로써 SSH 프로토콜 1을 사용할 수 없게할 수 있다. (버전 2는 2006년에 나왔으므로, 버전 1이 얼마나 예전 버전인지 가늠할 수 있다… 1->2는 보안 위주의 개선이 있었다고 한다.)

OpenSSH_8.2p1 Ubuntu-4ubuntu0.2, OpenSSL 1.1.1f 31 Mar 2020

Ubuntu 20.04에서의 SSHD (8.2p1) 에서는 이미 해당 옵션이 기본이고, 설정 파일에 존재하지 않는다.

Protocol 2가 활성화된 경우, ssh -1 sb@localhost로 접속을 시도하는 경우 SSH protocol v.1 is no longer supported가 표시되며 접속이 거부된다.


비밀번호 기반 접속 방식 미사용

비밀번호로 접속하는 방식은 브루트 포스 공격의 여지를 주게 되고, SSH Key 기반의 접속이 보편화된 요즘 필요성 또한 낮다.

PasswordAuthentication no 옵션을 주면 비밀번호로는 접속할 수 없게 된다. (이 글에서의 openssh server는 기본 설정으로 no이다.)


루트 계정으로의 접속 금지

루트 계정으로의 접속은 차단하는 것이 좋다. 애초에 루트 계정으로 접속할 수 있더라도, 접속하지 않는 것이 좋으므로 차단하도록 한다.

PermitRootLogin no 옵션을 주면 된다. (신기하게도 기본 옵션이 아니다.)

리눅스 - 시스템 로그와 로그인 로그

이 글은 리눅스에서 기본으로 작성되는 로그 파일에 대해 간단하게 소개한다. 로그 파일의 내용, 작성되는 구조와 그 활용 방법까지 알아보려고 했으나, 범위가 너무 커지는 것 같아 이후 글의 주제로 남겨두려 한다.


rsyslogd와 /var/log 디렉토리

rsyslog란 rocket-fast system for log processing의 약자로, 기존의 syslog 라는 로깅 데몬을 대체하는 시스템 로깅 데몬이다.

/var/log 디렉토리에 생성된 로그 파일은 rsyslog에 의해 작성된 것들이다. 시스템 데몬이, 시스템 서비스의 로그를 수집한 것이기 때문에 애플리케이션 로그는 포함되지 않는다.

아래는 syslog 파일 내용 중 일부이다.

1
2
3
4
Jun 20 11:52:14 instance-01 kernel: [    0.000000] On node 0 totalpages: 523966
Jun 20 11:52:14 instance-01 kernel: [ 0.000000] DMA zone: 64 pages used for memmap
Jun 20 11:52:14 instance-01 systemd[1]: Starting SSHGuard...
Jun 20 11:52:15 instance-01 ntp[1577]: * Starting NTP server ntpd

{ 출력 시간, 호스트명, 프로세스명, PID, 로그 내용 }

위의 로그를 보면 특정 형식으로 작성되어있고, 여러 프로세스에서 출력이 됨을 알 수 있다.

굳이 호스트명이 포함된 이유는 로그를 중앙 집중식으로 저장하고 분석, 시각화하는 로그 서버로의 전송을 고려했다고 한다. (참고로 syslog의 설정을 통해 원격 서버로 파일을 전송할 수 있다. 그리고, 여러 서버의 로그를 중앙에서 저장하는 경우 ntpdate 등의 유틸을 사용한 시간 동기화가 필수라고 한다.)

로그 내용은 아는 만큼 보이기 때문에, 또 리눅스의 자체 기능의 로그만이 들어있기 때문에 특정 metric을 잡고 이게 이런 의미이다.와 같은 판단을 내리기가 어려운 듯하다. 이건 로그를 공부할 게 아니라, 리눅스 자체의 기능을 공부해야 이해할 수 있는 부분으로 보인다.


로그인과 관련된 로그 모음

auth.log

인증 관련해선 (즉, SSH 위주) 로그 파일이 이 파일에 작성된다. 아마 단일 파일로는 auth.log가 제일 크지 않을까 싶은데, 대략 btmp(아래에서 소개)의 2배 정도의 파일 크기를 갖는 듯하다. (아무래도 SSH 접속 시도가 잦기 때문이다.)


로그인 성공 기록

last는 사용자 로그인 현황을 반환하는 명령어이다(성공한 로그인만 기록한다). wtmp 파일은 로그인, 로그아웃 기록이 저장되는 파일이다.


로그인 실패 기록

재밌는 점은 로그인 실패의 경우 lastb라는 별개의 명령어로 조회할 수 있다는 점이다. 또, 잘 보면 로그인 실패 기록이 매우 매우 많다. 얼마나 많냐면, wtmp 파일의 크기는 9.0K 인데, btmp는 576K 이다!


현재 로그인된 세션

명령어 who를 입력하면 현재 로그인 세션 정보를 출력한다. 세션 정보는 utmp 파일에 저장된다. 다만 utmp파일은 /var/log 디렉토리에서 바로 보이진 않았다.


SEE ALSO

test/test 계정을 만들어 놓고 해커가 어떤 형태로 접속하고 무슨 일을 했는 지 분석한 글: 우분투 리눅스 침투 사후 로그보기도 참고하면 재밌을 것 같다 :)


TODO

  1. ELK 기반으로 로그를 중앙 집중식으로 받아와 질의와 시각화를 할 수 있을 것 같은데 나중에 다뤄보려고 한다.

  2. sshd, common_auth 파일 무슨 PAM 개념이 나오던데, 잘 조사해서 SSH 보안 설정에 대해 다뤄보려고 한다.

리눅스 - ssh/sshd의 보안에 대해 (1/2)

이 글은 리눅스의 RDP나 다름 없는 SSH에 대해 간략히 소개하고, 보안 측면에서의 기능을 일부 다룬다.


SSH

SSH는 22번 포트를 사용하며 HTTPS와 같이 안전한 Layer를 감싼 Remote Shell이다. 접속 대상 Host는 SSH Daemon이 띄워져있어야 하며 OpenSSH(openssh-server)이 실질적 표준이다.


SSH 포트 변경

SSH를 인터넷 대상으로 Open(ALLOW 0.0.0.0)하는 경우 어떻게 알고 찾아왔는지, 아래와 같이 많은 곳에서 임의의 접근을 다수 받을 수 있다. 따라서 22번 포트에서 임의의 포트로 바꾸는 것이 기본이라고 할 수 있다. (물론 그래도 공격은 충분히 가능하다.)


SSH 로그 확인

SSH 로그는 sshd 접속 기록이 없는 경우 딱히 기록되지 않으니, 클라우드에서 SSH port를 Open해 놓고 하루 정도만 기다려도 로그가 많이 쌓이게 된다(ㅎㅎ..). 아래의 커맨드로 쉽게 확인할 수 있으며, auth.log 파일 자체는 다른 내용도 많이 담고 있으며, 파일이 커지면 auth.log.1과 같이 rolling 된다.

$ cat /var/log/auth.log | grep sshd

단순히 시도한 사용자명만 나열해도 이정도...


Bation Host 사용

로드밸런서, 배스쳔 호스트 예시


물론 접속 자체를 특정 네트워크 대역(고정 IP겠죠?)으로 제한하고, 특정 네트워크로 VPN을 사용해도 되겠지만, 그렇게 하기 어려운 경우라면 Bastion Host를 사용하는 것도 방법이다. 애플리케이션 서버 등은 보통 로드 밸런서 뒤에 숨기 때문에 굳이 외부로 노출되지 않는데 SSH 때문에 이를 Public Subnet으로 옮기는 것도 좋지 못하므로, 낮은 사양의 PC를 인터넷 대역에 오픈하고 해당 PC만이 내부망의 서버들과 통신할 수 있도록 하면 적절하다.


로드밸런서, 배스쳔 호스트 예시


SSH Proxy와 같은 여러 얘기가 있지만 간단하게 command를 실행시키는 형태로 쉽게 접속이 가능하다.

$ ssh -t user@BASTION_HOST ssh user@INTERNAL_SERVER


TODO

SSH의 많은 설정 중 보안에 관련된 설정을 더 알아보기

우분투의 SW 기반 방화벽 - UFW

이 글은 데비안 계열(우분투 등)에서 사용되는 ufw에 대해 다룬다. ufw는 iptables라는 유틸을 활용하는 유틸이고, iptables는 리눅스 커널의 netfilter라는 기능의 인터페이스(CLI)이다. 많은 사람들이 간단한 보안 수요에 대해서는 iptables의 복잡성 때문에 ufw를 사용하는 것을 권장한다.

iptables is a command line interface of netfilter, which is the underlying mechanics in the kernel for all of the work related. It has been (mostly) replaced by nftables (netfilter-tables) which provides more (advanced) features and a unified interface for IPv4/IPv6/ARP/Ethernet Bridges. But both iptables and nftables requires manually writing rules since they are kind of low-level.

UFW wraps around nftables (used to be iptables&ip6tables) and provides a much-easier-to-use command line interface for managing basic firewall functionalities, hence its name.

I personally recommend using UFW directly if all you want is a simple firewall. Otherwise you may use firewalld or write iptables/nftables rules directly.

출처: Quora


iptables는 아래 도표 중 hook API를 활용하는 인터페이스이다.

img


아래는 Netfilter와 리눅스 내, 외부의 처리 흐름이라고 한다.

img


Netfilter와 Iptables에 대해 더 자세히 알고 싶은 경우 iptables와 netfilter 정리 | 리눅스, 클라우드, IT 관련 기술 블로그를 참고하기 바란다.


1. 사용해야 할 때

ufw를 언제 써야 할까? 해당 Host의 앞 단에 보안 장비가 없고, 포트 접근 제어가 필요할 때이다. 리눅스를 클라이언트로 사용하는 경우 열린 포트가 없어 비교적 안전하지만 서버 단의 경우 그렇지 않다.


2. 간단한 사용법

ufw는 기본적으로 비활성화돼있는데, sudo ufw enable 로 활성화할 수 있다. 활성화 시 기본 설정만으로도 iptables의 NAT Table 설정이 자동 생성된다. 이는 ufw가 iptables의 wrapper이자 간단한 설정을 상세한 설정으로 변환시키는 역할을 담당함을 보여준다. (출처: 초보 시스템 관리자의 일기 | 우분투 방화벽 ufw로 시스템 보호하기)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
administrator@test02:~$ sudo iptables -L
Chain INPUT (policy DROP)
target prot opt source destination
ufw-before-logging-input all -- anywhere anywhere
ufw-before-input all -- anywhere anywhere
ufw-after-input all -- anywhere anywhere
ufw-after-logging-input all -- anywhere anywhere
ufw-reject-input all -- anywhere anywhere
ufw-track-input all -- anywhere anywhere

Chain FORWARD (policy DROP)
target prot opt source destination
ufw-before-logging-forward all -- anywhere anywhere
ufw-before-forward all -- anywhere anywhere
ufw-after-forward all -- anywhere anywhere
ufw-after-logging-forward all -- anywhere anywhere
ufw-reject-forward all -- anywhere anywhere

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
ufw-before-logging-output all -- anywhere anywhere
ufw-before-output all -- anywhere anywhere
ufw-after-output all -- anywhere anywhere
ufw-after-logging-output all -- anywhere anywhere
ufw-reject-output all -- anywhere anywhere
ufw-track-output all -- anywhere anywhere

Chain ufw-after-forward (1 references)
...
Chain ufw-after-input (1 references)
...
Chain ufw-after-logging-forward (1 references)
...

ufw의 사용법은 매우 간단하다. ufw {action} {port | serviceName}를 입력하면 된다. 이 때 서비스의 목록은 cat /etc/services로 확인할 수 있고, ufw deny ssh와 같이 사용할 수 있다. 아래와 같이 ufw도 status 명령을 지원한다. (iptables -L에 비해 가독성이 훨씬 낫다.)

ufw_deny_status


ufw는 기본적으로 whitelisting이지만, ufw default allow를 실행하면 blacklisting으로 전환할 수 있다.


ufw의 규칙을 제거하려면 ufw delete {규칙 내용} 으로 가능하다.

ufw_delete_deny


3. ‘우선 규칙’ 적용하기

보안 설정의 경우 우선 정의한 규칙이 적용된다고 한다. 즉, 순차적으로 ALLOW를 check하고, ALLOW되는 경우 통과하는 것이다. 이 경우 넓은 범위의 규칙은 뒤에 놓고, 좁은 범위의 (IP 차단 등) 규칙은 앞에 놓는 게 맞다.

‘우선 규칙’은 sudo nano /etc/ufw/before.rules에서 작성할 수 있다. End required lines 주석 아래에 -A ufw-before-input -s {Target_IP_Address, ...} -j DROP 을 추가하면 해당 IP를 모든 포트에 대해 접근 제한할 수 있다. (출처)


TODO

Logging에 대해 더 알아보기. (리눅스 로깅은 분량이 많고 어려운 것 같다.)

리눅스 파일 시스템의 구조 - ext 계열

이 글은 리눅스에서의 파일 시스템 중 ext 계열에 대해 설명한다.


1. ext2

ext2는 (ext를 개선한) 리눅스의 초기 파일 시스템이다. 개선된 버전인 ext3, ext4도 있으나, 많은 자료에서 ext2를 비중 있게 다루므로 이 글에서도 동일한 형태로 ext2를 위주로 서술한다.


ext disk layout


ext2는 디스크를 부트 섹터와 블록 그룹으로 분할한다. 블록 그룹이란, 일정한 크기의 블록의 집합을 의미한다. (정확히는 마지막 블록은 크기 제한에서 자유롭다.)

  • 파일 저장 시 동일한 블록 그룹 내에 저장하도록 스케줄링한다.

  • 블록 그룹은 아래의 필드를 갖는다. 아래의 필드 중 Super Block, Group Descriptor Table은 하나만 있어도 충분하나 백업 용도로 각 블록 그룹에 사본으로 저장된다(일관성 유지에 대한 내용은 잘 모르겠다.)

    필드명 설명
    Super Block 1KB의 크기의 자료구조로, 블록의 크기, 총 블록의 개수, 블록 그룹의 개수, i-node의 개수, 그룹 내 블록의 개수, 그룹 내 i-node의 개수 등의 정보를 저장한다.
    Group Descriptor Table Group Descriptor의 테이블이다. 각 Descriptor는 Block Bitmap(아래 필드)의 블록 번호, 그룹 내의 빈 블록 개수, 그룹 내의 i-node 개수, 그룹 안의 빈 디렉토리 개수를 저장한다.
    Block Bitmap 각 블록의 할당 여부를 bit(0,1)로 표시한다.
    i-node Bitmap i-node의 할당 여부를 bit로 표시한다.
    i-node Table 아래의 i-node를 테이블 형태로 저장한다.
    (actual) i-nodes (위의 테이블 형태로 저장된 것이다.) 128Byte의 크기를 가지며, 실제 파일/디렉토리의 위치를 저장한다. 파일의 메타 데이터를 보관한다. 각 파일/디렉토리는 i-node와 1:1로 대응되며 i-node를 알고 있다면 i-node에 저장된 블록 그룹을 확인할 수 있다. (자세한 필드는 아래 표에서 확인바란다.)
    (actual) blocks 실제로 데이터가 저장되는 블록이다.
  • i-node를 구성하는 필드는 아래와 같다.

    필드명
    Inode Number
    파일 모드
    하드 링크 개수
    소유자 정보
    파일 크기
    마지막 접근/수정 시각
    데이터 블록의 개수
    직접 블록 (대부분의 작은 파일은 직접 블록으로도 커버가 충분히 된다.)
    간접 블록 / 이중 간접 블록 / 삼중 간접 블록 (linked list 개념)

2. ext 2 vs 3,4

구조적으로 큰 차이보단 성능 개선 등에 초점이 맞춰져 있다.

features ext2 ext3 ext4
전원 문제 시 데이터 저장 손실 (저널링 파일 시스템 지원 여부) 문제 발생 저널링 지원됨 저널링 지원됨
파일 최대 크기 2TiB 2TiB 16TiB

Linux와 Docker의 기술적 관계 (2/3) - Network Namespace 기초

출처: ip로 직접 만들어보는 네트워크 네임스페이스와 브리지 네트워크

도커의 기반이 되는 네트워크 네임스페이스 기술에 대해 다룬다.

기준 환경:

Ubuntu 20.04.1 LTS (WSL2 by Windows 10 x64) (Docker Desktop)

일부 기능은 Native Docker에서만 정상 작동하여 아래 환경도 활용하였다.
Ubuntu 20.04.1 LTS (GCP Compute Engine) (Native Docker)


Network Namepsace의 역할

Network Namespace는 프로세스의 네트워크 환경을 분리한다. 기본적으로 프로세스의 네트워크 네임스페이스는 default 네임스페이스에 속한다.


Network Namepsace 사용 방법

아래에서 계속 사용하게 될 프로그램인 ip는 리눅스에서 네트워크를 관리하는 표준 유틸이며, 네트워크 네임스페이스 기능 또한 내장돼있다.


ip link : 네트워크 디바이스 목록 조회

이 결과는 위에서 말했듯, default 네트워크 네임스페이스에 속한 네트워크 디바이스의 목록이 출력된 것이다.


  1. ip netns add {NAMESPACE_TITLE} : 새로운 네트워크 네임스페이스 생성

  2. ip netns exec {NAMESPACE_TITLE} {COMMAND} : 특정 네트워크 네임스페이스에서 특정 명렁 수행

  3. ip link set dev ip netns exec {DEVICE_TITLE} {up|down} : 특정 네트워크 디바이스를 Enable/Disable 하도록 설정


Network Namepsace의 독립성

위에서 소개한 명령으로 네트워크 네임스페이스(direct_netns라고 아래에선 사용했다.)를 하나 생성한 후, default 네임스페이스에서 접속을 시도하는 경우 잘 되지 않는다. 이는 네임스페이스 간의 연결이 격리돼있기 때문이다.

약간의 셋업 과정으로 네트워크 네임스페이스의 격리 기능을 체험할 수 있다.

1
2
3
ip netns add direct_netns
ip netns exec direct_netns ip link set dev lo up
ip netns exec direct_netns nginx -g 'daemon off;'

위 커맨드를 통해 direct_netns 네임스페이스를 만들고, loopback 어댑터를 up 상태로 만들어주고, nginx를 실행하면 아래의 내용을 확인할 수 있다.

default 네트워크 네임스페이스에서는 curl 수행 시 접근할 수 없지만,

direct_netns 네트워크 네임스페이스에서는 curl 수행 시 정상 접근이 된다.

당연히, netstat -nat | grep LISTEN 의 수행 결과도 다르다.


Network Namepsace간의 연결

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# veth 생성 및 한 쪽(veth1)을 direct_netns로 이전
ip link add veth0 type veth peer name veth1
ip link set veth1 netns direct_netns

# IP 할당
ip a add 10.200.0.2/24 dev veth0
ip netns exec direct_netns ip a add 10.200.0.3/24 dev veth1

# Up 상태로 지정
ip link set dev veth0 up
ip netns exec direct_netns ip link set dev veth1 up

# ping 수행 (정상 작동 확인)
ping 10.200.0.3
ip netns exec direct_netns ping 10.200.0.2

veth(Virtual Ethernet Device)란 리눅스에서 사용하는 가상 네트워크 인터페이스를 의미하며, ip 명령어로 생성하는 것이 가능하다. 항상 쌍으로 만들어진다고 한다.

위와 같은 형태로 두 네트워크를 이어 통신할 수 있게도 할 수 있다.


TODO:

  • 브릿지 네트워크, NAT 등 소개

tmux 사용법 정리

tmux의 사용법을 알아본다. 그동안 너무 귀찮아서.. 여러 터미널이 필요할 때 그냥 여러 터미널을 사용했었는데, Ubuntu에 기본으로 내장돼있기도 하고 고수 분들이 기본으로 사용하시는 것들을 어깨 너머로 봤기 때문에 약간의 시간을 들여 학습했다.


1. 네트워크 연결 보호 기능

ssh 연결과 터미널 화면의 생명주기가 별개이다.

터미널을 실수로 꺼도, ssh 연결은 백그라운드로 살아 있다.

  • 새 터미널을 열어서 tmux at(tach) 로 다시 세션에 붙을 수 있다.
  • tmux at -t 세션넘버로 특정 세션을 종료할 수 있다.

2. 다중 화면 기능

  • Ctrl + b 를 통해 명령어 모드 활성

  • Ctrl + b 후 "를 누르면 새 가로 패널 생성 (%를 누르면 세로 패널 생성)

  • Ctrl + b + 방향키를 누르면 각 패널 전환

  • Ctrl + b + z를 누르면 현재 활성 패널을 전체화면으로 전환 (toggle)

  • Ctrl + b + Alt + 방향키를 누르면 현재 패널의 크기를 조절

  • Ctrl + b + space를 누르면 preset 패널 구성으로 배열함

  • Ctrl + b + d를 누르면 tmux에서 원래 터미널로 돌아옴. (tmux ls로 각 세션 넘버 확인을, tmux로 새 세션을 생성할 수도 있다.)

  • 새 세션인지는 tmux의 상태바에서 [0]의 숫자를 확인하면 된다. (이 경우 0).

  • Ctrl + b + x로 현재 패널을 제거할 수 있다. (마지막 패널 제거 시 현재 tmux 세션이 종료된다.)


TODO

  1. 셸 프로그래밍 기초 학습하기
  2. google/zx 써보고 가이드 작성하기
    • 너무 좋은 것 같다. async/await에 딱 맞는 자체 선언형 API에 nodejs를 그대로 사용할 수 있다는 점이 너무 좋다.

Linux와 Docker의 기술적 관계 (1/3) - Namespace

이 글은 리눅스의 Namespace 기능의 일부를 다룬다. 도커의 내용도 포함되겠으나 리눅스 자체의 기능에 주안점을 둔다. 우선 PID, UTS를 다루고, chroot에 대해서도 소개한다.

기준 환경:

Ubuntu 20.04.1 LTS (WSL2 by Windows 10 x64) (Docker Desktop)

일부 기능은 Native Docker에서만 정상 작동하여 아래 환경도 활용하였다.
Ubuntu 20.04.1 LTS (GCP Compute Engine) (Native Docker)


리눅스 컨테이너는 리눅스 네임스페이스와 루트 파일 시스템 격리 등의 기능을 사용해 격리시킨 프로세스를 의미한다.

namespace라는 기술에 대해

리눅스 네임스페이스(Linux Namespace)란?

Namespace는 한 개의 특정 프로세스에 대해 시스템 리소스를 논리적으로 격리하는 기능이다. 아래와 같이 여러 가지의 Namespace가 존재한다.

1
2
3
4
5
6
7
8
lrwxrwxrwx 1 root root 0 Apr  9 07:53 cgroup -> 'cgroup:[4026531835]' # cgroup = control group = CPU,RAM,NET 할당
lrwxrwxrwx 1 root root 0 Apr 9 07:53 ipc -> 'ipc:[4026532188]'
lrwxrwxrwx 1 root root 0 Apr 9 06:41 mnt -> 'mnt:[4026532186]'
lrwxrwxrwx 1 root root 0 Apr 9 07:53 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Apr 9 07:53 pid -> 'pid:[4026532189]'
lrwxrwxrwx 1 root root 0 Apr 9 08:35 pid_for_children -> 'pid:[4026532189]'
lrwxrwxrwx 1 root root 0 Apr 9 07:53 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 9 07:53 uts -> 'uts:[4026532187]' # uts=Unix Time-Sharing / Host, Domain name 할당

Docker에서 사용하는 Namespace의 목록은 아래와 같다.

  • ipc, mnt, net, pid, pid_for_children, uts

PID Namespace

  1. 분리된 새로운 네임스페이스에서는 PID=1부터 시작하나, 기존 네임스페이스에서도 (다른 PID로) 해당 프로세스를 볼 수 있으며, 이는 PID=1부터 시작하지 않는다.

  2. ls -alh /proc/{PID}/ns - PID에 대응되는 프로세스가 속한 네임스페이스를 출력한다. (sym link 형태의 구성)

  • 위의 결과는 같은 1번 프로세스에서 생성된 경우 모두 같은 값을 갖는다.

    1
    2
    $ diff <(ls -Al /proc/1/ns | awk '{ print $11 }') \ 
    <(ls -Al /proc/{임의의_프로세스ID}/ns | awk '{ print $11 }')
  1. User namespace는 UID 0을 줘서 root 같아 보이는 - 어느 정도 제한된 - 권한을 제공할 수 있다. User namespace는 initial이 있고, 매 생성마다 Parent-Child 구조를 갖는다.

  2. PID namespace 격리를 위해선 격리된 디렉토리에서의 모든 의존성이 준비 돼야 한다. - 의존성? docker Image가 하는 일이 바로 그것이니, docker Image를 활용해 mount한 폴더를 활용할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    $ docker run ~
    $ docker export ~ > ~.tar
    $ tar xf ~
    $ sudo unshare -p -f --mount-proc chroot . ./image_extracted_dir /bin/sh

    # echo $$
    > 1
  3. unshare 명령어를 써서 PID를 격리해 1번 프로세스가 되는 예시:

    1
    2
    3
    4
    5
    $ unshare -p -f --mount-proc chroot . /bin/sh
    > echo $$ : 1

    $ unshare -p /bin/sh
    > echo $$: 3481
  4. 각종 옵션들 정리

    1. p: PID 네임스페이스는 분리가 되었을텐데 로 짐작해보건데 PID 네임스페이스를 의미하는 듯하다.
    2. f: --fork의 의미로, unshare의 child process로 실행한다고 한다.
    3. --mount-proc: /proc 이 기본값이어서, 기존 proc과 격리될 수 없다. 위 명령어에선 현재 폴더(.)를 넘겼다.
    • man page에선 The new proc filesystem is explicitly as private라고 하는데, 여기서 private이 무슨 뜻인지 모르겠다. 그리고, mount proc filesystem한다는 표현도 무슨 뜻인지 잘 모르겠다.
    • shell이 지원하는 명령어가 아니라 바이너리를 실행하려고 하면 2: Cannot fork라고 뜬다. Why?
    1. Host Shell에서 ps -ef하면 unshare도 하나의 프로세스로 뜨고, sudo kill -9 3481 하면 unshare도 종료된다.
    2. 프로세스 3481 == 프로세스 1 (unshare도 단지 하나의 프로세스)
    3. /proc 이 기본 PID namepsace이고, init 프로세스(pid 1)에 의해 pid namespace가 다 동일하다.
    4. unshare로 다른 디렉토리에 PID namespace를 mount하면 그 때부터 pid namepsace 값이 다른 값으로 할당됨

UTS Namepsace

출처: UTS 네임스페이스를 사용한 호스트네임 격리 - 컨테이너 네트워크 기초 1편

UTS Namespace는 호스트 네임을 분리한다. 딱 이것만 한다. 별로 기능은 없다.

docker는 컨테이너마다 컨테이너ID로 호스트네임을 부여한다. 이 기능을 구현하는 방법은 무엇일까? nsenter로 가능하다. nsenter는 unshare로 지정한 네임스페이스 정보를 기반으로 프로세스를 실행하는 프로그램이다.

  1. unshare --uts=/tmp/utsns1 hostname utsns1 명령으로 utsns1로 새로운 namespace를 정의하고 실행할 수 있다.
  2. /tmp/utsns1 파일에 unshare 설정을 저장한 후,
  3. nsenter --uts=/tmp/utsns1 hostname 수행 시 utsns1를 출력할 수 있다.

Network Namepsace

Network Namespace는 프로세스의 네트워크 환경을 분리한다. 실제로 docker 컨테이너마다 네트워크 인터페이스와 IP가 부여된다.

  • (2편에서 확인할 예정)

TODO:

  • 네트워크 부분 더 공부하기

리눅스에서의 sudo와 권한 관리 (1)

이 글은 리눅스에서 sudo를 소개하고, 권한 관리에 대해 생각해본다. 예시 중에서 사용자를 생성하는 방법 또한 다룬다.

참고 자료

기준 환경:

Ubuntu 20.04.1 LTS (WSL2 by Windows 10 x64)


su와 sudo는 약자이다. su는 “substitute user”, sudo는 “substitute user do”이다.

리눅스에서의 su 사용법과 사용자 생성

su는 현재 로그인된 사용자에서 다른 사용자로 로그인하는 것이다.

사용자 목록은 cat /etc/passwd로 볼 수 있다. (이름과 다르게 비밀번호가 나오진 않는다.)

각 사용자의 암호화된 비밀번호를 확인하려면 cat /etc/shadow를 확인하라.

어떤 암호화가 쓰였는지는 cat /etc/pam.d/common-password로 확인할 수 있다 - 기준 환경에서는 sha-512를 사용한다. (출처: What methods are used to encrypt passwords in /etc/passwd and /etc/shadow?)


재밌는 점은, 사용자가 직접 만든 계정이 아닌 경우 로그인할 수 없게 되있다는 점이다. (출처: What are the default passwords of these users: nobody, daemon, uucp, etc.?)

모든 그룹의 이름만 출력한 경우

만약 ‘사람이 사용하는 용도의’ 사용자 목록을 확인하려면, cat /bin/bash /etc/passwd로 확인할 수 있다.

시스템 계정 제외하고 출력


사용자를 추가해서, 해당 사용자로 로그인해보자.

1
2
3
4
5
sudo useradd tmp # tmp는 유저명이다. 나머지는 자동으로 생성해준다. (단, 비밀번호가 없는 상태이다.)
sudo passwd tmp # 로그인을 하려면 비밀번호를 설정해야 한다. passwd 명령어로 비밀번호를 지정할 수 있다.
su tmp
echo $0 # bash가 사라져서, 현재 셸을 출력해봤다. 기본 셸은 dash인걸로 아는데, /bin/sh를 사용하고 있다.
bash # bash shell로 전환한다.

이렇게 로그인하면 bash가 사라지고, sh가 셸로 사용된다. (chsh로 바꿀 수 있다.)


사용자가 sudo를 사용할 수 있게 만들기, sudo의 권한에 대하여

sudo는 특정 작업을 root 권한으로 수행할 수 있게 하는 명령어이다. 특정 사용자를 sudo 그룹에 추가하기만 하면 된다: sudo usermod -aG sudo {사용자명}. 이후 사용자는 sudo 명령어를 사용할 수 있게 된다.


sudo 파일의 내용

각 유저에 대한 권한은 /etc/sudoers에 있는데, 권한의 포멧이 있다: %sudo ALL=(ALL:ALL) ALL 이것인데, (출처: How To Edit the Sudoers File)

%sudo ALL= (ALL: ALL) ALL
username 규칙 hostname 규칙 sudo로 로그인할 수 있는 사용자 목록 sudo로 로그인할 수 있는 그룹 목록 root 권한으로 사용할 수 있는 명령어

지금 알고 있는 지식으로는 sudo 파일의 Best Practice를 소개는 어렵기 때문에 추후로 미루지만, 권한 제어를 폭 넓게 할 수 있도록 필드가 5개나 된다는 점은 확인할 수 있었다. (아직 한 번도 sudoer 설정을 만져본 적이 없다는 점은 좀 부끄럽기도 하다.)


sudo의 장점

출처: Best practices for hardening sudo?

  1. sudo는 Root Password 공유를 막는다.

  2. syslog 등의 명령을 통해, sudo를 사용하는 경우 원격 서버로 로그를 전송하게 해서 더 빠르게 침입을 알아낼 수 있다.

  3. 제대로 권한 설정만 한다면 Root 권한보다 많이 축소되므로 침입자가 모든 기록을 말소하지 못하게 할 수 있다. (온전한 root 권한을 탈취 당한 경우 아무 소용이 없게 된다.)

위 출처의 아래 답변에서 얻은 사실인데, ssh_user 그룹을 만들어 remote login만 가능하게 하면 공격자가 ssh credential을 탈취하더라도 sudo 명령을 활용할 수 없기 때문에 보안 상 더 안전하다고 한다. (굉장히 유용한 방법인 듯하다.)


TODO:

  • 프로덕션 배포용 서버에 적절한 sudoer 설정에 대해 찾아보기
  • sudo 등의 예시 외에도 리눅스 상에서 보안 관련한 기능과 설정들을 찾아보기