앞 시간에 쿠버네티스 젠킨스 에이전트에서 별도의 인증 없이 Docker sock에 바로 접근을 해보았습니다.
실무에서는 말도 안 되는 일이죠.
이번 단계를 위한 초석이었습니다.
이번 시간에는 도커 데몬에 보안인증서(TLS)를 적용해 봅시다.
인증서 부분은 참고한 블로그의 정리가 예술이라 굳이 다시 정리하지 않아도 좋겠지만 제 스타일의 기록 차원에서 남깁니다.
https://blog.naver.com/alice_k106/220743690397
Docker 데몬 측에서는 서버측 인증서와 키, 클라이언트는 클라이언트 측 인증서와 키가 필요하고 공통적으로 ca.pem 이 필요합니다. 즉 우리에게 필요한 파일들은 모두 5개, ca.pem, server-cert.pem, server-key.pem, cert.pem, key.pem 입니다.
모든 파일은 도커 호스트 내부에서 생성합니다.
# /root/.docker 또는 /ubuntu/.docker 등으로 이동합니다.
cd ~/.docker
# ca-key.pem
❯ openssl genrsa -aes256 -out ca-key.pem 4096
Enter PEM pass phrase: # 4자 이상 입력
Verifying - Enter PEM pass phrase: # 동일하게 입력
# ca.pem
❯ openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
Enter pass phrase for ca-key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]: kyungi
Locality Name (eg, city) []:Yongin
Organization Name (eg, company) [Internet Widgits Pty Ltd]:codelab
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:master
Email Address []:master@code-lab.kr
# server-key.pem(서버 키)
❯ openssl genrsa -out server-key.pem 4096
# server.csr(인증요청서)
❯ openssl req -subj "/CN=<호스트 프라이빗IP>" -sha256 -new -key server-key.pem -out server.csr
❯ openssl req -subj "/CN=10.0.0.111" -sha256 -new -key server-key.pem -out server.csr
# extfile.cnf(설정)
# docker socket 접근할 때 사용할 호스트 주소를 입력합니다.
❯ echo subjectAltName = IP:<호스트 프라이빗IP>,IP:<호스트 퍼블릭IP>IP:IP:127.0.0.1 > extfile.cnf
❯ echo subjectAltName = IP:10.0.0.111,IP:131.0.0.131,IP:127.0.0.1 > extfile.cnf
# server-cert.pem(인증서)
❯ openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
Certificate request self-signature ok
subject=CN = 10.0.0.131
Enter pass phrase for ca-key.pem:
# 파일 생성 확인
❯ ubuntu@dev-instance ~/.docker 6s
❯ ll
total 40
-rw------- 1 ubuntu ubuntu 3434 Feb 11 07:52 ca-key.pem
-rw-rw-r-- 1 ubuntu ubuntu 2106 Feb 11 07:55 ca.pem
-rw-rw-r-- 1 ubuntu ubuntu 92 Feb 11 08:11 extfile.cnf
-rw-rw-r-- 1 ubuntu ubuntu 1996 Feb 11 08:19 server-cert.pem
-rw------- 1 ubuntu ubuntu 3272 Feb 11 07:58 server-key.pem
-rw-rw-r-- 1 ubuntu ubuntu 1590 Feb 11 08:02 server.csr
❯ openssl genrsa -out key.pem 4096
❯ openssl req -subj '/CN=client' -new -key key.pem -out client.csr
❯ echo extendedKeyUsage = clientAuth > extfile.cnf
❯ openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf
# 불필요한 파일(인증요청서) 삭제 및 권한 변경
❯ rm -v client.csr server.csr
❯ chmod -v 0400 ca-key.pem key.pem server-key.pem
❯ chmod -v 0444 ca.pem server-cert.pem cert.pem
❯ sudo systemctl stop docker
❯ sudo systemctl stop docker.socket
❯ sudo dockerd --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem -H=0.0.0.0:2376
INFO[2024-02-11T08:40:07.855290838Z] Starting up
INFO[2024-02-11T08:40:07.856362280Z] detected 127.0.0.53 nameserver, assuming systemd-resolved, so using resolv.conf: /run/systemd/resolve/resolv.conf
INFO[2024-02-11T08:40:07.886786792Z] [graphdriver] using prior storage driver: overlay2
INFO[2024-02-11T08:40:07.887629554Z] Loading containers: start.
INFO[2024-02-11T08:40:08.285653779Z] Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address
INFO[2024-02-11T08:40:08.383387931Z] Loading containers: done.
INFO[2024-02-11T08:40:08.398611647Z] Docker daemon commit=f417435 containerd-snapshotter=false storage-driver=overlay2 version=25.0.3
INFO[2024-02-11T08:40:08.398693847Z] Daemon has completed initialization
INFO[2024-02-11T08:40:08.425919472Z] API listen on [::]:2376
# 새 터미널을 열어서 로컬 클라이언트(docker ps할 때 docker 역시 도커 소켓을 이용하는 클라이언트입니다.)에 인증서를 적용하여 `docker version`을 실행합니다.
❯ docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=10.0.0.131:2376 version
Client: Docker Engine - Community
Version: 25.0.3
API version: 1.44
Go version: go1.21.6
Git commit: 4debf41
Built: Tue Feb 6 21:13:11 2024
OS/Arch: linux/arm64
Context: default
Server: Docker Engine - Community
Engine:
Version: 25.0.3
API version: 1.44 (minimum version 1.24)
Go version: go1.21.6
Git commit: f417435
Built: Tue Feb 6 21:13:11 2024
OS/Arch: linux/arm64
Experimental: false
containerd:
Version: 1.6.28
GitCommit: ae07eda36dd25f8a1b98dfbf587313b99c0190bb
runc:
Version: 1.1.12
GitCommit: v1.1.12-0-g51d5e94
docker-init:
Version: 0.19.0
GitCommit: de40ad0
여기까지 블로그 참고하였습니다. 감사합니다! :)
위 테스트 설정은 터미널이 종료되면 함께 종료됩니다. systemctl을 이용하여 시스템이 재부팅되어도 설정이 유지되도록 해봅시다.
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo vi /etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --tlsverify --tlscacert=/home/ubuntu/.docker/ca.pem --tlscert=/home/ubuntu/.docker/server-cert.pem --tlskey=/home/ubuntu/.docker/server-key.pem -H=0.0.0.0:2376
# 이전 테스트 터미널이 실행중이면 ctl + c로 중지
sudo systemctl daemon-reload
sudo systemctl stop docker
sudo systemctl stop docker.socket
sudo systemctl start docker
❯ sudo systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Drop-In: /etc/systemd/system/docker.service.d
└─override.conf
Active: active (running) since Sun 2024-02-11 09:05:41 UTC; 14s ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 27300 (dockerd)
Tasks: 10
Memory: 27.4M
CPU: 306ms
CGroup: /system.slice/docker.service
└─27300 /usr/bin/dockerd --tlsverify --tlscacert=/home/ubuntu/.docker/ca.pem ->
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.157625480Z" level=in>
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.159107481Z" level=in>
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.196797676Z" level=in>
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.197569197Z" level=in>
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.644679089Z" level=in>
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.713241552Z" level=in>
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.725225763Z" level=in>
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.725314523Z" level=in>
Feb 11 09:05:41 dev-instance dockerd[27300]: time="2024-02-11T09:05:41.753324389Z" level=in>
Feb 11 09:05:41 dev-instance systemd[1]: Started Docker Application Container Engine
매번 docker ps 등을 할 때마다
docker --tlsverify --tlscacert=/home/ubuntu/.docker/ca.pem --tlscert=/home/ubuntu/.docker/cert.pem --tlskey=key.pem -H=10.0.0.131:2376 ps
이렇게 입력해야 하는데 매우 불편합니다.
vi ~/.zshrc 또는 vi ~/.bashrc
# Docker
export DOCKER_HOST=10.0.0.111:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=/home/ubuntu/.docker
source ~/.zsh
docker ps를 실행해 봅니다.
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
동일 네트워크 내에서는 위와 같은 방식을 사용할 수 있지만 서로 다른 네트워크라면 다른 방식으로 접근해야 합니다.
docker 호스트 내부에서는 인증을 사용하지 않고 외부와의 통신만 tls 인증을 적용해 보겠습니다.
도커 호스트에 내부와 외부의 연결하는 역할을 하는 socat 컨테이너 만들어 tls server 역할로 지정합니다.
socat 컨테이너의 포트와 도커 호스트의 포트를 바인딩합니다.
클라이언트(젠킨스 도커 클라우드)에서 tls 인증서를 사용하여 도커 호스트 및 포트로 접근하면 원격으로 도커 소켓을 사용할 수 있습니다.
이를 위해 지금까지의 tls 적용설정을 역순으로 초기화합니다.
vi ~/.zshrc 또는 vi ~/.bashrc
# Docker
# export DOCKER_HOST=10.0.0.111:2376
export DOCKER_TLS_VERIFY=0
# export DOCKER_CERT_PATH=/home/ubuntu/.docker
source ~/.zsh
sudo rm /etc/systemd/system/docker.service.d/override.conf
sudo systemctl daemon-reload
sudo systemctl stop docker
sudo systemctl stop docker.socket
sudo systemctl start docker
docker run --name socat-docker -d --restart=always --publish 2376:2375 --network dev-net \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /home/ubuntu/.docker/server-cert.pem:/cert.pem \
-v /home/ubuntu/.docker/server-key.pem:/key.pem \
-v /home/ubuntu/.docker/ca.pem:/ca.pem \
alpine/socat openssl-listen:2375,fork,reuseaddr,cert=/cert.pem,key=/key.pem,cafile=/ca.pem,verify=1 unix-connect:/var/run/docker.sock
cd ~/.docker && vi Dockerfile.agent
FROM jenkins/agent:latest-jdk17
USER root
COPY ./ca.pem /ca.pem
COPY ./cert.pem /cert.pem
COPY ./key.pem /key.pem
ENV DOCKER_TLS_VERIFY=1
ENV DOCKER_CERT_PATH=/
ENV DOCER_HOST=<도커호스트 퍼블릭IP>:2376
RUN chown jenkins:jenkins /ca.pem /cert.pem /key.pem && \
chmod 644 /ca.pem /cert.pem /key.pem
RUN apt-get update && apt-get install -y lsb-release && \
curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \
https://download.docker.com/linux/debian/gpg && \
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \
https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list && \
apt-get update && apt-get install -y docker.io && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
usermod -aG docker jenkins
USER jenkins
docker build -f Dockerfile.agent -t codelabmaster/jenkins-agent-docker .
docker push codelabmaster/jenkins-agent-docker
클라이언트 인증서, 키, 서버 ca를 입력합니다.
tcp://<도커호스트 퍼블릭IP>:2376
pipeline {
agent {
label 'docker'
}
stages {
stage('Test') {
steps {
sh 'docker run hello-world'
}
}
}
}
Started by user Jenkins Admin
[Pipeline] Start of Pipeline
[Pipeline] node
Running on docker-0002f4btxs70t on docker in /home/jenkins/agent/workspace/test-docker
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] sh
+ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm64v8)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
문제현상
❯ docker ps
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json": dial unix /var/run/docker.sock: connect: permission denied
대응방법
도커 그룹에 젠킨스 유저 권한 추가
# Add jenkins to docker user group
# 도커 그룹 권한 확인
sudo su -
cat /etc/group
docker:x:999
# 반영 확인
cat /etc/group
docker:x:999:ubuntu
# docker 소켓 user를 root에서 현재 사용자로 변경
sudo chown ubuntu:docker /var/run/docker.sock