레지스트리 인증에 키클록 사용하기
- 레지스트리 인증 방식
- 레지스트리 인증 절차 설명
- 키클록 설치
- 키클록의 구성요소
- 키클록에서 인증서 조회
- docker-v2 프로토콜 설정
- User 추가
- Registry auth 설정
- 인증 방법
- Source
도커 레지스트리를 안전하게 사용하기 위해 인증을 적용할 수 있습니다. 인증 방식에는 여러 가지가 있는데 아래 내용에서는 키클록으로 인증서버를 따로 두는 방식을 살펴볼 것입니다. 키클록은 Red Hat에서 개발한 오픈 소스로 아이덴티티 및 액세스 관리 플랫폼입니다. Single Sign-On(SSO) 지원 등 광범위한 인증과 권한 부여 기능들을 지원합니다.
레지스트리 인증 방식
도커 레지스트리는 Basic Auth와 Bearer Token 방식을 통해 인증을 제공합니다.
- Basic Auth: 사용자 이름과 비밀번호를 사용하여 인증하는 가장 기본적인 방식입니다.
- Bearer Token: 보다 복잡한 시나리오와 권한 관리를 위해 사용됩니다. 토큰 서비스에서 발급받은 토큰을 사용하여 인증합니다.
아래 내역 중 1번이 Basic Auth에 해당하며 2~4번이 Bearer Token을 사용하는 방식입니다. 이어지는 예제에서는 2번을 사용하되 Docker Auth가 아닌 키클록을 사용해서 인증하는 방식을 살펴볼 예정입니다.
- 기본 인증 (Basic Authentication)
- 이는 사용자 이름과 비밀번호를 사용한 가장 간단한 인증 방식입니다.
- NGINX나 Apache와 같은 웹 서버 또는 리버스 프록시를 사용하여 기본 인증을 설정할 수 있습니다.
- 기본 인증을 사용할 경우, 비밀번호를 안전하게 저장하는 것이 중요합니다. 예를 들어, htpasswd 도구를 사용하여 암호화된 비밀번호를 생성할 수 있습니다.
- 토큰 기반 인증
- Docker의 표준 인증 방식으로, Docker CLI가 레지스트리에 요청을 보낼 때마다 인증 서버에서 토큰을 요청하게 됩니다.
- 이 방식은 Docker Auth와 같은 인증 서버를 사용하여 구성할 수 있습니다.
- OAuth / OIDC
- OAuth 또는 OpenID Connect (OIDC)를 사용하여 레지스트리에 대한 액세스를 인증하고 관리할 수 있습니다.
- 이는 특히 대규모 조직이나 클라우드 기반의 인프라에서 유용하며, 사용자 및 서비스 계정의 액세스를 중앙에서 관리할 수 있게 해줍니다.
- 클라이언트 인증서
- 클라이언트 인증서를 사용하면, 특정 클라이언트만 레지스트리에 액세스할 수 있도록 할 수 있습니다.
- 이 방식은 매우 안전하지만, 인증서를 관리하는 것이 복잡할 수 있습니다.
레지스트리 인증 절차 설명
도커 레지스트리의 토큰 기반 인증 절차는 Docker 공식 문서를 참조하였습니다.
- 도커 클라이언트는 레지스트리에 push/pull 작업을 시작하려고 시도합니다.
- 레지스트리가 인증을 요구하면 401 Unauthorized HTTP 응답과 함께 인증하는 방법에 대한 정보를 반환합니다.
- 클라이언트는 인증 서비스에 토큰을 요청합니다.
- 인증 서비스는 클라이언트의 특정 권한을 나타내는 Bearer 토큰을 반환합니다.
- 클라이언트는 요청의 Authorization 헤더에 Bearer 토큰을 포함하여 원래의 요청을 다시 시도합니다.
- 레지스트리는 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 탭을 클릭하면 인증서를 조회할 수 있습니다.
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로 선택합니다.
Installation 탭에서 Registry Config File을 보면 docker auth 설정에 넣을 값들을 확인할 수 있습니다.
User 추가
레지스트리에 로그인할 유저를 추가하는 방법에 대해 설명하겠습니다.
- 키클록 어드민에 로그인합니다.
- Manage 항목의 Users 탭을 클릭합니다.
- Add user를 선택하여 유저를 추가합니다.
- Credentials 탭에서 비밀번호를 설정합니다. 이 비밀번호는 레지스트리에 로그인할 때 사용되므로, Temporary 설정은 Off로 해서 나중에 비밀번호를 바꾸지 않도록 설정합니다.
Registry auth 설정
레지스트리의 auth 설정은 키클록과 도커 레지스트리 간의 인증 통신을 설정하기 위해 필요합니다. 아래는 해당 설정에 대한 예제입니다.
위에서 살펴본 Registry Config File과 시크릿을 통해 파일로 저장한 루트 인증서를 rootcertbundle로 참조해서 작성할 수 있습니다.
"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}