Jenkins 3-3 + Docker TLS

Flexyz·2024년 2월 11일
0

Jenkins

목록 보기
8/11

앞 시간에 쿠버네티스 젠킨스 에이전트에서 별도의 인증 없이 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 명령 입력 간소화

매번 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 인증서를 사용하여 도커 호스트 및 포트로 접근하면 원격으로 도커 소켓을 사용할 수 있습니다.


Docker 호스트 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

tls 적용 socat 컨테이너 실행

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

K8S 젠킨스 클라우드 도커에 원격도커 호스트 및 tls 적용

클라이언트 인증서, 키, 서버 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
profile
Think about a better architecture

0개의 댓글