본문 바로가기
Kubernetes

Self Sign 인증서 적용하기 - 구성

by journalctl 2025. 3. 24.

설정

Cert-Manager

먼저 Cert-Manager에서 사용하는 개념을 정리하면 다음과 같다.

개념 설명
Issuer 네임스페이스 범위에서 인증서를 발급할 수 있게 해주는 리소스
ClusterIssuer 클러스터 전체 범위에서 인증서 발급 가능
Certificate 실제로 발급받고자 하는 인증서 정의
issuerRef 어떤 Issuer를 사용할지 지정
  1. SelfSigned CA 발급을 위한 ClusterIssuer 생성
# selfsigned-clusterissuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-cluster-issuer
spec:
  selfSigned: {}
  1. CA Certificate 발급
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: root-ca
  namespace: cert-manager
spec:
  isCA: true
  commonName: "Private CA Cert"
  secretName: root-ca-secret
  duration: 8760h # 1년
  privateKey:
    algorithm: RSA
    size: 2048
  issuerRef:
    name: selfsigned-cluster-issuer
    kind: ClusterIssuer
  1. CA를 통해 발급한 인증서의 Issuer 등록
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: ca-cluster-issuer
spec:
  ca:
    secretName: root-ca-secret
  1. GitLab 인증서 발급 (SelfSigned Root CA)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: gitlab-wildcard-cert
  namespace: gitlab
spec:
  secretName: gitlab-wildcard-tls
  commonName: "*.local.example"
  dnsNames:
    - "*.local.example"
    - "gitlab.example.com"  # 루트 도메인도 포함하려면 명시적으로 작성
  issuerRef:
    name: ca-cluster-issuer
    kind: ClusterIssuer
  1. GitLab Workspace 인증서 발급 (SelfSigned Root CA)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: gitlab-workspace-wildcard-cert
  namespace: gitlab-workspaces
spec:
  secretName: gitlab-workspace-wildcart-tls
  commonName: "*.workspace.local.example"
  dnsNames:
    - "*.workspace.local.example"
    - "workspace.local.example"  # 루트 도메인도 포함하려면 명시적으로 작성
  issuerRef:
    name: ca-cluster-issuer
    kind: ClusterIssuer

Trust-Manager

  1. Root CA Bundle 생성 (각 Namespace 배포)
apiVersion: trust.cert-manager.io/v1alpha1
kind: Bundle
metadata:
  name: ca-pemstore
spec:
  sources:
    - useDefaultCAs: true
    - inLine: |
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----
  target:
    configMap:
      key: "ca-certificates.crt"

Kyverno

  1. ClusterPolicy 적용 (Admission Webhook)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-ca-certificates-volume
spec:
  background: false
  rules:
    - name: add-ca-certificates
      match:
        resources:
          kinds:
            - Deployment
            - StatefulSet
            - DaemonSet
            - Pod
      mutate:
        patchStrategicMerge:
          spec:
            initContainers:
              - (name): "*"
                volumeMounts:
                  - name: etc-ssl-certs
                    mountPath: /etc/ssl/certs/ca-certificates.crt
                    subPath: ca-certificates.crt
                    readOnly: true
            containers:
              - (name): "*"
                volumeMounts:
                  - name: etc-ssl-certs
                    mountPath: /etc/ssl/certs/ca-certificates.crt
                    subPath: ca-certificates.crt
                    readOnly: true
            volumes:
              - name: etc-ssl-certs
                configMap:
                  name: ca-pemstore

Windows 적용 (Client)

  1. CA.Cert 추출하기 (CA Certificate 발급 시 생성됐던 Secret)
# ca.crt
$ kubectl get secret root-ca-secret -n cert-manager -o jsonpath='{.data.ca\.crt}' | base64 --decode > ca.crt
$ cat ca.crt
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
  1. Windows 인증서 관리 매니저 실행
certmgr.msc
  1. 발급한 Root CA 인증서 추가

신뢰할 수 있는 루트 인증기관 > 인증서 > 우클릭 후 가져오기

 

ca.crt 추가 후 인증서 설치 진행

 


확인

  1. Kyverno Admission Controller

앞서 생성한 Kyverno Policy (add-ca-certificates-volume)가 적용되어 Webhook - Mutation 단계에서 Resource를 수정한다.

$ k get po -n kyverno -l app.kubernetes.io/component=admission-controller
NAME                                          READY   STATUS    RESTARTS       AGE
kyverno-admission-controller-df7b67cf-7q629   1/1     Running   2 (6h8m ago)   3d21h

$ k logs kyverno-admission-controller-df7b67cf-7q629 -n kyverno
...
2025-03-24T02:03:22Z INF github.com/kyverno/kyverno/pkg/webhooks/resource/mutation/mutation.go:136 > mutation rules from policy applied successfully URLParams= clusterroles=["cluster-admin","system:basic-user","system:discovery","system:public-info-viewer"] gvk="apps/v1, Kind=Deployment" gvr={"group":"apps","resource":"deployments","version":"v1"} kind=Deployment logger=webhooks/resource/mutate name=gitlab-registry namespace=gitlab operation=CREATE policy=add-ca-certificates-volume resource.gvk="apps/v1, Kind=Deployment" roles=[] rules=["add-ca-certificates"] uid=b9599936-0a53-4c13-9c42-2f6a6be198af user={"groups":["kubeadm:cluster-admins","system:authenticated"],"username":"kubernetes-admin"} v=0

2025-03-24T02:03:22Z INF github.com/kyverno/kyverno/pkg/webhooks/resource/mutation/mutation.go:136 > mutation rules from policy applied successfully URLParams= clusterroles=["cluster-admin","system:basic-user","system:discovery","system:public-info-viewer"] gvk="apps/v1, Kind=Deployment" gvr={"group":"apps","resource":"deployments","version":"v1"} kind=Deployment logger=webhooks/resource/mutate name=gitlab-gitlab-shell namespace=gitlab operation=CREATE policy=add-ca-certificates-volume resource.gvk="apps/v1, Kind=Deployment" roles=[] rules=["add-ca-certificates"] uid=831bc9f5-29cb-4644-a55e-f34552fb5e7d user={"groups":["kubeadm:cluster-admins","system:authenticated"],"username":"kubernetes-admin"} v=0
...
  1. 수정된 Resource 살펴보기

Kyverno Policy에 설정한 대로 “etc-ssl-certs” Volume이 추가되어 생성되었다.

Name:             gitlab-gitlab-shell-f56dd8dbc-7w56x
Namespace:        gitlab
Priority:         0
Service Account:  default
Node:             test-worker/172.18.0.2
Labels:           app=gitlab-shell
                  chart=gitlab-shell-8.8.1
Controlled By:  ReplicaSet/gitlab-gitlab-shell-f56dd8dbc
Containers:
  gitlab-shell:
    Container ID:   containerd://c6c49e0b2d1f86c2e6b6adaa6aa8591500fc0da028fee94fdd46e06dd7be8fab
    Image:          registry.gitlab.com/gitlab-org/build/cng/gitlab-shell:v14.39.0
    Image ID:       registry.gitlab.com/gitlab-org/build/cng/gitlab-shell@sha256:466e09ede4852a497fd48190afb9653a94cc7008133ff174aee53023c48069a9
    Port:           2222/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Mon, 24 Mar 2025 11:14:41 +0900
    Ready:          True
    Restart Count:  0
    Requests:
      cpu:      0
      memory:   6M
    Liveness:   exec [/scripts/healthcheck] delay=10s timeout=3s period=10s #success=1 #failure=3
    Readiness:  tcp-socket :2222 delay=10s timeout=3s period=5s #success=1 #failure=2
    Environment:
      GITALY_FEATURE_DEFAULT_ON:  1
      CONFIG_TEMPLATE_DIRECTORY:  /etc/gitlab-shell
      CONFIG_DIRECTORY:           /srv/gitlab-shell
      KEYS_DIRECTORY:             /etc/gitlab-secrets/ssh
      SSH_DAEMON:                 openssh
      TZ:                         UTC
    Mounts:
      /etc/gitlab-secrets from shell-secrets (ro)
      /etc/gitlab-shell from shell-config (rw)
      /etc/krb5.conf from shell-config (ro,path="krb5.conf")
      /etc/pki/ca-trust/extracted/pem from etc-pki-ca-trust-extracted-pem (ro)
      /etc/ssh/sshd_config from sshd-config (ro,path="sshd_config")
      /etc/ssl/certs/ from etc-ssl-certs (ro)
Volumes:
  shell-config:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      gitlab-gitlab-shell
    Optional:  false
  sshd-config:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      gitlab-gitlab-shell-sshd
    Optional:  false
  shell-init-secrets:
    Type:                Projected (a volume that contains injected data from multiple sources)
    SecretName:          gitlab-gitlab-shell-host-keys
    SecretOptionalName:  <nil>
    SecretName:          gitlab-gitlab-shell-secret
    SecretOptionalName:  <nil>
  shell-secrets:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     Memory
    SizeLimit:  <unset>
  etc-ssl-certs:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:     Memory
    SizeLimit:  <unset>
  etc-pki-ca-trust-extracted-pem:
    Type:        EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:      Memory
    SizeLimit:   <unset>
  1. TLS 확인

정상적으로 TLS HandShaking 확인.

CAfile: /etc/ssl/certs/ca-certificates.crt (Kyverno Policy에서 설정한 volumeMount 위치)

(Client hello → Server hello → Finished)

$ kubectl run tmp-shell --rm -i --tty --image nicolaka/netshoot

# gitlab.local.example, *.local.example 도메인에 대해서 인증서를 발급받았음
$ curl -v https://gitlab.local.example
* Host gitlab.local.example:443 was resolved.
* IPv6: (none)
* IPv4: 172.18.0.2
*   Trying 172.18.0.2:443...
* Connected to gitlab.local.example (172.18.0.2) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / RSASSA-PSS

후기

몇 줄 작성안했는데 잘 되는구만..

 

Kyverno ClusterPolicy를 사용해서 생성되는 리소스에 대해서 CA 인증서를 추가하도록 Admission WebHook을 통해 간단하게 구성해보았다.

 

TLS, mTLS는 서비스 메시 도구 (ex. Istio)를 사용해서 구성하는 방안이 더 깔끔한 방안이 될 것 같다.

 

이번에 Kyverno의 ClusterPolicy를 통해서 Admission Webhook을 따로 구성하지 않아도 간단한 Yaml 구성을 통해 수행할 수 있어 보안 정책을 수립할 때 괜찮은 도구인 것 같다.