도커 레지스트리를 안전하게 사용하기 위해 인증을 적용할 수 있습니다. 인증 방식에는 여러 가지가 있는데 아래 내용에서는 키클록으로 인증서버를 따로 두는 방식을 살펴볼 것입니다. 키클록은 Red Hat에서 개발한 오픈 소스로 아이덴티티 및 액세스 관리 플랫폼입니다. Single Sign-On(SSO) 지원 등 광범위한 인증과 권한 부여 기능들을 지원합니다.

레지스트리 인증 방식

도커 레지스트리는 Basic Auth와 Bearer Token 방식을 통해 인증을 제공합니다.

  • Basic Auth: 사용자 이름과 비밀번호를 사용하여 인증하는 가장 기본적인 방식입니다.
  • Bearer Token: 보다 복잡한 시나리오와 권한 관리를 위해 사용됩니다. 토큰 서비스에서 발급받은 토큰을 사용하여 인증합니다.

아래 내역 중 1번이 Basic Auth에 해당하며 2~4번이 Bearer Token을 사용하는 방식입니다. 이어지는 예제에서는 2번을 사용하되 Docker Auth가 아닌 키클록을 사용해서 인증하는 방식을 살펴볼 예정입니다.

  1. 기본 인증 (Basic Authentication)
    • 이는 사용자 이름과 비밀번호를 사용한 가장 간단한 인증 방식입니다.
    • NGINX나 Apache와 같은 웹 서버 또는 리버스 프록시를 사용하여 기본 인증을 설정할 수 있습니다.
    • 기본 인증을 사용할 경우, 비밀번호를 안전하게 저장하는 것이 중요합니다. 예를 들어, htpasswd 도구를 사용하여 암호화된 비밀번호를 생성할 수 있습니다.
  2. 토큰 기반 인증
    • Docker의 표준 인증 방식으로, Docker CLI가 레지스트리에 요청을 보낼 때마다 인증 서버에서 토큰을 요청하게 됩니다.
    • 이 방식은 Docker Auth와 같은 인증 서버를 사용하여 구성할 수 있습니다.
  3. OAuth / OIDC
    • OAuth 또는 OpenID Connect (OIDC)를 사용하여 레지스트리에 대한 액세스를 인증하고 관리할 수 있습니다.
    • 이는 특히 대규모 조직이나 클라우드 기반의 인프라에서 유용하며, 사용자 및 서비스 계정의 액세스를 중앙에서 관리할 수 있게 해줍니다.
  4. 클라이언트 인증서
    • 클라이언트 인증서를 사용하면, 특정 클라이언트만 레지스트리에 액세스할 수 있도록 할 수 있습니다.
    • 이 방식은 매우 안전하지만, 인증서를 관리하는 것이 복잡할 수 있습니다.

레지스트리 인증 절차 설명

도커 레지스트리의 토큰 기반 인증 절차는 Docker 공식 문서를 참조하였습니다.

  1. 도커 클라이언트는 레지스트리에 push/pull 작업을 시작하려고 시도합니다.
  2. 레지스트리가 인증을 요구하면 401 Unauthorized HTTP 응답과 함께 인증하는 방법에 대한 정보를 반환합니다.
  3. 클라이언트는 인증 서비스에 토큰을 요청합니다.
  4. 인증 서비스는 클라이언트의 특정 권한을 나타내는 Bearer 토큰을 반환합니다.
  5. 클라이언트는 요청의 Authorization 헤더에 Bearer 토큰을 포함하여 원래의 요청을 다시 시도합니다.
  6. 레지스트리는 Bearer 토큰과 그 안에 포함된 권한 세트를 검증하여 클라이언트를 인증하고, push/pull 세션을 시작합니다.

키클록 설치

apiVersion: v1
kind: Secret
metadata:
  name: keycloak-secrets
  namespace: reg
type: Opaque
data:
  KEYCLOAK_USER: YWRtaW4=       # 'admin'의 Base64 인코딩 값
  KEYCLOAK_PASSWORD: YWRtaW4=   # 'admin'의 Base64 인코딩 값
  DB_USER: cm9vdA==             # 'root'의 Base64 인코딩 값
  DB_PASSWORD: ..                 
  MYSQL_USER: ..
  MYSQL_PASSWORD: ..
  MYSQL_ROOT_PASSWORD: .. 
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: keycloak-mysql-pv-volume
  namespace: reg
spec:
  storageClassName: standard
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/keycloak-mysql"

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: keycloak-mysql-pv-claim
  namespace: reg
spec:
  storageClassName: standard
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: keycloak-mysql
  namespace: reg
spec:
  serviceName: keycloak-mysql-service
  replicas: 1
  selector:
    matchLabels:
      app: keycloak-mysql
  template:
    metadata:
      labels:
        app: keycloak-mysql
    spec:
      containers:
      - image: mysql:8.0
        name: keycloak-mysql
        env:
          - name: MYSQL_USER
            valueFrom:
              secretKeyRef:
                name: keycloak-secrets
                key: MYSQL_USER
          - name: MYSQL_PASSWORD
            valueFrom:
              secretKeyRef:
                name: keycloak-secrets
                key: MYSQL_PASSWORD
          - name: MYSQL_ROOT_PASSWORD
            valueFrom:
              secretKeyRef:
                name: keycloak-secrets
                key: MYSQL_ROOT_PASSWORD
          - name: MYSQL_DATABASE
            value: "keycloak"
        ports:
          - containerPort: 3306
            name: mysql
        volumeMounts:
          - name: mysql-storage
            mountPath: /var/lib/mysql
      volumes:
        - name: mysql-storage
          persistentVolumeClaim:
            claimName: keycloak-mysql-pv-claim

---
apiVersion: v1
kind: Service
metadata:
  name: keycloak-mysql-service
  namespace: reg
spec:
  type: NodePort
  ports:
    - port: 3306
      nodePort: 32314
  selector:
    app: keycloak-mysql
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  namespace: reg
spec:
  replicas: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
      - name: keycloak
        image: jboss/keycloak:latest
        args:
        - "-Dkeycloak.profile.feature.docker=enabled"
        - "-b"
        - "0.0.0.0"
        env:
        - name: KEYCLOAK_USER
          valueFrom:
            secretKeyRef:
              name: keycloak-secrets
              key: KEYCLOAK_USER
        - name: KEYCLOAK_PASSWORD
          valueFrom:
            secretKeyRef:
              name: keycloak-secrets
              key: KEYCLOAK_PASSWORD
        - name: DB_VENDOR
          value: "mysql"
        - name: DB_ADDR
          value: "keycloak-mysql-service.reg.svc.cluster.local"
        - name: DB_DATABASE
          value: "keycloak"
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: keycloak-secrets
              key: DB_USER
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: keycloak-secrets
              key: DB_PASSWORD

        ports:
        - name: https
          containerPort: 8443

        volumeMounts:
        - mountPath: "/etc/x509/https"
          name: tls
          readOnly: true

      volumes:
      - name: tls
        secret:
          secretName: user-registry-tls

---
apiVersion: v1
kind: Service
metadata:
  name: keycloak
  namespace: reg
spec:
  type: NodePort
  ports:
  - name: https
    port: 8443
    targetPort: 8443
    nodePort: 32123
  selector:
    app: keycloak

키클록의 구성요소

  • Realm: 키클록에서 가장 큰 관리 단위. 사용자, 그룹, 애플리케이션, 역할 등의 관리 범위를 정의합니다.
  • Client: 연결할 애플리케이션 또는 서비스를 의미합니다. 각 클라이언트는 별도의 인증 설정을 가질 수 있습니다.
  • User: 인증을 수행할 사용자. 사용자는 여러 가지 속성과 자격 증명을 가질 수 있습니다.

키클록에서 인증서 조회

키클록 어드민에 접속해서 로그인 후에 Realm에 registry를 추가하고 Realm settings 탭을 클릭합니다. keys 탭을 클릭하면 인증서를 조회할 수 있습니다.

도커인증_1

keycloak api를 사용해서 정보를 가져올 수도 있는데 Keycloak의 REST API 문서를 참고하여 다음과 같은 방식으로 수행할 수 있습니다.

  • create_keycloak_registry_cert_secret: 키클록에서 가져온 RS256 인증서를 Kubernetes Secret으로 저장하는 함수입니다.
  • find_rs256_certificate: 인증서 데이터에서 RS256 알고리즘을 사용하는 인증서를 찾아 반환하는 함수입니다.
  • get_keycloak_registry_cert: 키클록에서 현재 Realm에 대한 인증서 데이터를 가져오는 함수입니다.
def create_keycloak_registry_cert_secret(self, namespace):  
    rs256_cert_str = get_keycloak_registry_cert()  
  
    if not rs256_cert_str.startswith("-----BEGIN CERTIFICATE-----"):  
        rs256_cert_str = "-----BEGIN CERTIFICATE-----\n" + rs256_cert_str + "\n-----END CERTIFICATE-----"  
  
    secret_data = {  
        "registry-cert.pem": base64.b64encode(rs256_cert_str.encode('utf-8')).decode('utf-8')  
    }  
  
    secret = client.V1Secret(  
        metadata=client.V1ObjectMeta(name="registry-cert-secret"),  
        data=secret_data  
    )  
    return self.v1.create_namespaced_secret(namespace=namespace, body=secret)


def find_rs256_certificate(cert_data):  
    for key in cert_data.get("keys", []):  
        if key.get("alg") == "RS256":  
            return key.get("x5c")[0]  
    return None  
  
  
def get_keycloak_registry_cert():  
    keycloak_cert_url = f"{server_info.client.REG_AUTH}/auth/realms/registry/protocol/openid-connect/certs"  
    cert_data = requests.get(keycloak_cert_url, verify=False).json()  
    return find_rs256_certificate(cert_data)

이 인증서를 Kubernetes Secret으로 저장한 후, docker auth 설정의 rootcertbundle에 해당 Secret의 경로를 지정합니다.

docker-v2 프로토콜 설정

키클록에서는 도커 레지스트리 인증을 위해 docker-v2 프로토콜을 사용할 수 있습니다. 이 프로토콜은 도커 인증을 위한 특화된 프로토콜로, 키클록과 도커 레지스트리 간의 통신을 설정할 때 사용됩니다.

키클록 설치 시 yaml에 docker 설정을 추가합니다.

spec:
  containers:
  - name: keycloak
	image: jboss/keycloak:latest
	args:
	- "-Dkeycloak.profile.feature.docker=enabled"
	- "-b"
	- "0.0.0.0"

client를 추가할 때 프로토콜을 docker-v2로 선택합니다.

도커인증_2

Installation 탭에서 Registry Config File을 보면 docker auth 설정에 넣을 값들을 확인할 수 있습니다.

도커인증_3

User 추가

레지스트리에 로그인할 유저를 추가하는 방법에 대해 설명하겠습니다.

  1. 키클록 어드민에 로그인합니다.
  2. Manage 항목의 Users 탭을 클릭합니다.
  3. Add user를 선택하여 유저를 추가합니다.

도커인증_4

  1. Credentials 탭에서 비밀번호를 설정합니다. 이 비밀번호는 레지스트리에 로그인할 때 사용되므로, Temporary 설정은 Off로 해서 나중에 비밀번호를 바꾸지 않도록 설정합니다.

도커인증_5

Registry auth 설정

레지스트리의 auth 설정은 키클록과 도커 레지스트리 간의 인증 통신을 설정하기 위해 필요합니다. 아래는 해당 설정에 대한 예제입니다.

위에서 살펴본 Registry Config File과 시크릿을 통해 파일로 저장한 루트 인증서를 rootcertbundle로 참조해서 작성할 수 있습니다.

Registry Auth

"auth": {  
    "token": {  
        "realm": f"{host}/auth/realms/{keycloak_realm}/protocol/docker-v2/auth",  
        "service": keycloak_client,  
        "issuer": f"{host}/auth/realms/registry",  
        "rootcertbundle": "/certs/registry-cert.pem"  
    }  
},

인증 방법

레지스트리에 접근하기 위해선, 도커 CLI를 사용하여 레지스트리에 로그인해야 합니다. 아래의 명령어를 통해 로그인할 수 있습니다.

docker login {host} -u {user-name} -p {user-pw}

Source