[CKS] Kubernetes 시스템을 강화하는 방법(1)

쿠버네티스 클러스터 보안을 위한 최소 권한 원칙을 구현하는 방법을 다룹니다. 노드 접근 제한, 사용자 권한 관리, SSH 보안, 불필요한 서비스 제거, 커널 모듈 블랙리스트, 포트 비활성화, 방화벽 설정 등을 통해 Attack Surface를 최소화합니다.

[CKS] Kubernetes 시스템을 강화하는 방법(1)
Photo by Jonny Gios / Unsplash

개요

CKS 자격증을 위해 아래 내용을 정리했습니다.

최소 권한 원칙

이전 글에선 명시적인 접근 제어, 인증, 암호화를 수행했습니다.

명시적인 제어 외 시스템에서 묵시적으로 수행되어야하는 내용을 정리했습니다.

쿠버네티스 클러스터와 같은 컴퓨터 시스템의 최소 권한 표준입니다.

보안 조치 설명
노드 접근 제한 무단 수정을 방지하기 위해 노드에 사용자 권한이 제한되어 있는지 확인하십시오.
역할 기반 접근 제어(RBAC) 클러스터 내 사용자 및 서비스에 대한 정확한 접근 권한을 정의합니다.
사용되지 않는 패키지를 제거 더 이상 필요하지 않은 소프트웨어를 제거하여 시스템을 최신 상태로 유지하십시오.
네트워크 접근 제한 구성 요소 간 네트워크 통신을 제한하여 Attack surface를 줄이십시오.
커널 모듈 제한 필수 커널 모듈만 로드하고 불필요한 모듈은 차단합니다.
열린 포트 수정 무단 침입을 방지하기 위해 개방된 포트를 식별하고 보안을 강화하십시오.

호스트 OS 최소화

시스템의 기능을 보강하는 것이 아닌 최소화하고 단순화하는 방법입니다.

이에 대한 모범 사례를 정리했습니다.

노드 접근 제한

ABAC, RBAC과 같은 기능은 k8s의 사용자 권한을 관리하는데 도움이 됩니다. 먼저 노드 보안을 강화하는 방법에 대해 위주로 작성했습니다.

최상의 보안을 위해선 Control Plane과 Node 모두 프라이빗한 환경으로 구성해야합니다.

특히 Control Plane 노드는 기본적으로 접근할 수 없어야합니다.

노드 접근을 제어하는 방법은 크게 두 가지 레벨로 존재합니다.

인프라/네트워크 레벨

인프라 레벨에서 노드 접근을 제한하는 방법은 흔히 AWS 같은 클라우드 벤더사에서 제공되곤 합니다.

  • 인터넷과 연결되지 않은 환경에서 구축
  • 신뢰할 수 있는 네트워크 구축(VPN)
  • 승인된 네트워크(Allow - 10.0.0.0/16, Deny - 허용된 것 외)

호스트(각 노드) 레벨

연결 제한은 인프라/네트워크가 가장 효과적이고 범용적이지만 호스트에서 직접 구성할 수도 있습니다.

만약 리눅스를 사용한다면 사용자 계정 분류에 따라 관리할 수 있습니다.

  • 사용자 계정 → 사용자가 특정 사용자를 위해 생성하는 계정
  • 슈퍼유저 계정 → 시스템에 대한 완전 접근 권한을 가진 계정
  • 시스템 계정 → OS 설치 시 자동 생성되는 운영체제의 기본 서비스 전용 계정
  • 서비스 계정 → 특정 서비스를 설치 시 자동 생성되는 해당 서비스 전용 계정

당연하지만 루트로 실행하면 안됩니다.

⇒ 서비스 해킹 시 공격자가 시스템 전체를 장악 가능하고 피해가 커질 수 있습니다.

사용자 검사

호스트 레벨에서 감사 데이터, 사고 방지를 확인하기 위한 명령어입니다.

  • id명령은 UID, GID 및 그룹 멤버십과 같은 중요한 사용자 정보를 표시합니다.
  • who 명령은 현재 시스템에 로그인한 사용자 목록을 보여줍니다.
  • last 명령어는 사용자의 로그인 기록을 보여줍니다

id 파일과 who, last는 모두 접근 권한이 있는 계정의 감사 기록이 존재합니다.

불필요한 계정은 제거하거나 비활성화 하는 것을 권장합니다.

접근 제어

사용자 검사 시 아래 정보도 같이 확인하는게 좋습니다.

  • /etc/passwd : 사용자 이름, UID, GID, 홈 디렉터리, 기본 셸과 같은 기본 사용자 정보가 포함되어 있습니다(참고: 암호 정보는 경로에 없습니다)
    • 모든 사용자가 읽을 수 있음
      • UID 0: root 계정
      • UID 1-999 (또는 1-499): 시스템 계정 (서비스용)
      • UID 1000+ (또는 500+): 일반 사용자 계정
  • /etc/shadow : 해시 처리된 사용자 암호를 저장합니다
    • 루트만 읽을 수 있음(비밀번호 보호)
  • /etc/group: 그룹 이름, GID 및 멤버 연결을 포함한 그룹 목록을 표시합니다
grep -i ^michael /etc/passwd
# Output:
grep -i ^michael /etc/shadow
# Output:
grep -i ^bob /etc/group
# Output:
# developer:x:1001:bob,michael
sudo passwd david # 비밀번호 재지정

불필요한 사용자 관리 방안

<유저 생성 방법(참고)>

sudo useradd -m -d /opt/sam -s /bin/bash -u 2328 -G admin sam
옵션 의미
-m 홈 디렉토리 생성
-d /opt/sam 홈 디렉토리 경로 지정
-s /bin/bash 로그인 쉘 지정
-u 2328 UID 지정
-G admin 보조 그룹(admin) 추가
sam 사용자 이름

<유저 비활성화 & 삭제>

# -s는 shell(기본 셸)을 /bin/nologin으로 변경하여 로그인을 차단
usermod -s /bin/nologin michael
id michael
# Output: # michael:x:1001:1001::/home/michael:/bin/nologin

그룹에서 관리자 권한만 제거하는 방법

deluser michael admin
# Output:
# Removing user `michael` from group `admin` ... Done.

deluser <user>
delgroup <group>

이러한 명령은 OS 로컬 수준으로 적용됩니다. 엔터프라이즈 레벨에선 Single Sign On 기반의 Active Directory 또는 LDAP와 같은 디렉터리 서비스를 통해 관리하는 것이 좋습니다.

SSH 원격 접속 제어

ssh key 기반으로 생성하는 것이 좋습니다

ssh-keygen -t rsa
sudo vi /etc/ssh/sshd_config

# 루트 로그인 금지
PermitRootLogin no
# 암호 기반 인증 비활성화
PasswordAuthentication no

sudo systemctl restart sshd
# identity file
ssh -i /path/to/private_key user@hostname
passwd jim # 비밀번호 재생성
usermod -aG sudo jim # 기존 그룹 유지 + sudo 추가

Privilege Escalation in Linux(권한 상승)

권한 관리 방법

루트를 사용하는 것은 보안 위험을 초래하기에 루트 사용자 로그인을 비활성화했습니다.

하지만 소프트웨어나 시스템 유지 관리를 위해선 관리자 권한이 필요합니다.

이에 가장 효과적인 방법은 sudo를 사용하는 것 입니다. sudo를 사용하면 신뢰할 수 있는 사용자가 자신의 암호를 입력하여 관리자 권한을 가진 명령을 실행할 수 있으므로 보안이 강화될 뿐만 아니라 수행된 작업에 대한 감사 기록도 남게 됩니다.

/etc/sudoers 를 기반으로 구분합니다. 구분되는 내용은 다음과 같습니다.

  • 누가 sudo를 사용할 수 있는지
  • 어떤 명령어를 실행할 수 있는지
  • 어떤 호스트에서 실행할 수 있는지
cat /etc/sudoers
User privilege specification
root    ALL=(ALL:ALL) ALL
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL
# Allow mark to run any command
mark    ALL=(ALL:ALL) ALL
# Allow Sarah to reboot the system
sarah localhost=/usr/bin/shutdown -r now
# See sudoers(5) for more information on "#include" directives
#include /etc/sudoers.d
  1. 사용자 또는 그룹(%): 첫 번째 필드는 권한을 부여받을 사용자 또는 그룹(그룹은 접두사로 시작)을 지정합니다 .
  2. *호스트 지정:***ALL**두 번째 필드는 일반적으로 로 설정되며, 권한이 모든 호스트(일반적으로 로컬 호스트에 한정됨)에 적용됨을 나타냅니다.
  3. 실행 사용자 지정: 괄호로 묶인 세 번째 필드는 명령을 실행할 사용자를 지정합니다. "ALL"은 모든 사용자로 명령을 실행할 수 있음을 의미합니다.
  4. 명령어 지정: 네 번째 필드는 허용되는 명령어를 지정합니다. "ALL"을 사용하면 모든 명령어가 허용되지만, Sarah 항목에서처럼 특정 사용자만 특정 명령어를 사용하도록 제한할 수도 있습니다.

(참고) 항상 **visudo**명령어를 사용하는 것을 권장합니다. visudo의 경우 문법 검사 등을 지원합니다.

사용하지 않는 패키지 제거하기

Notion Image

불필요한 패키지와 서비스를 제거하여 Attack Surface를 줄여야합니다.

최소한으로 유지하며 정기적인 감사, 최신 보안 패치로 업데이트할 수 있어야합니다.

서비스 관리를 위한 systemd

최신의 리눅스에서는 systemd를 사용하여 서비스를 관리합니다.

systemctl 로 서비스 상태를 확인하고 필수 서비스 시작/중지하여 포괄적인 제어를 수행할 수 있습니다.

  • /lib/systemd/system/apache2.service - 주요 구성파일 위치
$ systemctl status apache2

Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
Drop-In: /lib/systemd/system/apache2.service.d
 └─apache2-system.conf
Active: active (running) since Mon 2021-03-29 18:01:14 UTC; 1s ago
Process: 19026 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCCESS)
Main PID: 19037 (apache2)
Tasks: 55 (limit: 7372)
CGroup: /system.slice/apache2.service
 ├─19037 /usr/sbin/apache2 -k start
 ├─19038 /usr/sbin/apache2 -k start
 └─19039 /usr/sbin/apache2 -k start

설치된 모든 서비스를 확인할 수 있습니다.

sudo apt list --installed
$ systemctl list-units --type service

apache2.service         loaded active running   The Apache HTTP Server
apparmor.service        loaded active exited    AppArmor initialization
containerd.service      loaded active running   containerd container runtime
dbus.service            loaded active running   D-Bus System Message Bus
docker.service          loaded active running   Docker Application Container Engine
ebtables.service        loaded active exited    ebtables ruleset management
kmod-static-nodes.service loaded active exited   Create list of required static device nodes
kubelet.service         loaded active running   kubelet: The Kubernetes Node Agent
proxy.service           loaded active running   kubectl proxy 8888
systemd-journal-flush.service loaded active exited Flush Journal to Persistent Storage

불필요한 서비스를 비활성화 하는 방법은 다음과 같습니다.

$ systemctl stop apache2 # 서비스 일시 중지
$ systemctl disable apache2 # 부팅될 때 서비스를 시작하지 않음

이후 서비스를 제거합니다.

$ rm /lib/systemd/system/nginx.service
$ apt remove apache2

혹은 업데이트

apt update

커널 모듈 제한하기

시스템 보안 위해 Linux 커널 모듈의 사용을 제한해야합니다.

동적으로 확장되는 커널의 특성상 Attack Surface가 확대될 수 있습니다. 따라서 불필요한 모듈은 blacklist 설정으로 제거해야합니다. Pod 내부에서 네트워크 프로토콜 관련 모듈(SCTP, …)이 자동으로 로드될 수 있어 이를 방지하는 방법을 작성했습니다.

커널 모듈 로딩 및 목록

커널 모듈은 관리자가 수동으로 또는 커널 모듈이 자동으로 로드합니다.

modprobe 모듈을 로드하는 코드는 다음과 같습니다.

$ modprobe pcspkr

lsmod 로드된 모듈을 확인 가능합니다.

$ lsmod

Module                  Size  Used by
floppy                 69417  0
xt_conntrack           16384  1
ipt_MASQUERADE         16384  1
nf_nat_masquerade_ipv4 16384  1 ipt_MASQUERADE
nf_conntrack_netlink   40960  0
nfnetlink              16384  2 nf_conntrack_netlink
xfrm_user              32768  1
xfrm_algo              16384  1 xfrm_user
xt_addrtype            16384  2
iptable_filter         16384  1
iptable_nat            16384  1
nf_conntrack_ipv4      16384  3
nf_defrag_ipv4         16384  1 nf_conntrack_ipv4
nf_nat_ipv4            16384  1 iptable_nat

커널 모듈 블랙리스트

/etc/modprobe.d/ 하위에 *.conf 형식으로 등록합니다.

$ cat /etc/modprobe.d/blacklist.conf
$ blacklist sctp # $ echo "blacklist evbug" >> /etc/modprobe.d/blacklist.conf

여러 모듈 등록하기

$ cat /etc/modprobe.d/blacklist.conf
$ blacklist sctp
$ blacklist dccp
$ shutdown -r now
$ lsmod | grep dccp

모듈은 부팅 시 적용되기에 재부팅이 필수적입니다.

재부팅하지 않으면 모듈이 계속 활성화될 수 있으니 주의가 필요합니다.

 Kubernetes용 CIS 벤치마크 3.4절 참

오픈된 포트를 식별과 비활성화

사용하지 않는 포트를 비활성화 하는 방법을 알아봅니다.

netstat을 통한 활성 포트 확인

현재 리스닝 중인 사이트는 다음과 같습니다.

$ netstat -an | grep -w LISTEN

tcp        0      0 127.0.0.1:10248         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10249         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:2379          0.0.0.0:*               LISTEN
tcp        0      0 10.53.64.6:2379         0.0.0.0:*               LISTEN
tcp        0      0 10.53.64.6:2380         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:42893         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:2381          0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.11:46607        0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10257         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:10259         0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp6       0      0 :::10250                :::*                    LISTEN
tcp6       0      0 :::6443                 :::*                    LISTEN
tcp6       0      0 :::10256                :::*                    LISTEN
tcp6       0      0 :::22                   :::*                    LISTEN
tcp6       0      0 :::8888                 :::*                    LISTEN=

특정 포트 확인 가능합니다.

$ netstat -tlnp | grep 9090

tcp6       0      0 :::9090                 :::*                    LISTEN      21042/apache2

포트 사용처 파악

/etc/services 로 서비스가 어디서 실행되는지 확인할 수 있습니다.

$ /etc/services 

tcpmux          1/tcp            # TCP port multiplexer
ftp             21/tcp
ssh             22/tcp           # SSH Remote Login Protocol
telnet          23/tcp
smtp            25/tcp           # mail
domain          53/tcp           # Domain Name Server
domain          53/udp
http            80/tcp           # WWW HTTP
https           443/tcp          # HTTP over TLS/SSL

확인해서 시스템에 불필요하거나 사용하지 않는 서비스 포트는 비활성화하거나 차단해야합니다.

또한 새 소프트웨어를 설치하기 전에 어떤 포트를 열어 두어야 하는지 확인하는 것이 매우 중요합니다.

항상 소프트웨어 공식 문서를 참고하는 것이 좋습니다.

IAM 역할 최소화

AWS와 같은 퍼블릭 클라우드 플랫폼에서의 최소 권한 원칙을 의미합니다.

평소 AWS를 자주 사용하므로 간단하게 정리했습니다. 핵심 개념은 다음과 같습니다.

  • 루트 및 IAM 사용자 역할 이해하기
  • IAM 사용자 및 그룹 생성
    • IAM 그룹을 사용하여 권한 관리를 간소화
  • AWS 리소스에 대한 권한 관리
  • 지속적인 검토 및 감사

외부 네트워크 최소화

서버의 네트워크 접근을 제한하는 도구와 기술을 간단하게 정리했습니다.

바인딩된 포트 확인

서비스 기준으로 확인

  • systemctl status ssh
  • cat /etc/services | grep ssh
ssh             22/tcp                     # SSH Remote Login Protocol

포트 기준으로 확인

  • netstat -an | grep -w LISTEN
  • netstat -tlnp | grep 9090
tcp6       0      0 :::9090                 :::*                    LISTEN      21042/apache2

네트워크 보안 접근 방식

여러 라우터와 스위치를 통해 실제 환경에서 계층형 보안 조치를 구성하는 것이 좋습니다.

네트워크 보안을 강화하기 위해선 크게 2가지 접근 방식이 존재합니다.

  • 네트워크 수준의 보안
    • 외부 방화벽이나 보안 어플라이언스를 사용
      • 네트워크 전체의 트래픽 흐름 제어와 트래픽 모니터링
      • Cisco ASA, Juniper NextGen Firewall, Barracuda NextGen Firewall, Fortinet
  • 서버 수준의 보안
    • iptables, firewalld 또는 UFW 같은 방화벽. Windows 서버의 내장 방화벽
    • 클라우드의 보안 그룹

UFW(Uncomplicated Firewall)

리눅스 방화벽 관리 규칙인 UFW를 정리했습니다.

UFW는 IPTables와 Linux 내부 패킷 필터링 시스템인 Netfilter를 사용합니다. 즉 아래와 같은 구조를 가지고 있습니다.

  • UFW → IPTables → Netfilter

ufw의 기본 설정 방법

ufw는 설치가 필요합니다.

$ apt-get install ufw
$ ufw status

Status: inactive

방화벽 규칙 구성 방법은 아래와 같습니다.

$ ufw default allow outgoing # 아웃바운드 기본 허용
$ ufw default deny incoming # 인바운드 기본 거부

구체적인 허용 규칙입니다.

  • Bastion host에서 22번 포트 허용
  • Bastion host에서 80번 포트 허용
  • 내부 CIDR에서 80번 포트 허용
    • to any → 서버로 들어오는 모든 요청을 의미합니다.
$ ufw allow from 172.16.238.5 to any port 22 proto tcp
$ ufw allow from 172.16.238.5 to any port 80 proto tcp
$ ufw allow from 172.16.100.0/28 to any port 80 proto tcp

구체적인 차단 규칙입니다.

default deny라고 해도 명시적으로 차단하는 것이 좋습니다. 차단 의도를 명확히 할 수 있기 때문입니다.

$ ufw deny 8080

ufw 활성화

ufw를 활성화 하기 전 기존 연결이 차단되거나 끊기는 것을 유념해야합니다.

$ ufw enable

시스템에서 UFW를 활성화하면 기존 SSH 연결이 중단될 수 있다고 경고합니다.

Command may disrupt existing ssh connections. Proceed with operation (y|n)? y

활성화 상태를 검토할 수 있습니다.

$ ufw status

Status: active
To                         Action      From
--                         -----       ----
22/tcp                     ALLOW       172.16.238.5
80/tcp                     ALLOW       172.16.238.5
80/tcp                     ALLOW       172.16.100.0/28
8080                       DENY        Anywhere
8080 (v6)                  DENY        Anywhere (v6)

ufw 규칙 삭제

  1. 규칙 내용을 직접 명시해서 삭제
  2. 규칙 번호로 삭제
$ ufw delete deny 8080
$ ufw delete 5