1.6.1 Secret Store로 OCI Vault 사용하기
OKE에서는 Secret를 etcd에서 관리, 저장하며, 암호화 키를 통해 암호화해서 저장하고 있습니다. 더불어 Secret이 YAML상에 포함되어 사용되는 경우가 많은데, 이것이 보안상의 문제가 우려되는 경우, 외부 Secret 저장소를 사용하여 관리하고자 하는 요구사항이 있습니다. OKE에서는 OCI Secrets Store CSI Driver Provider를 통해 OCI Vault에서 Secret을 저장하고 사용하는 것을 지원하고 있습니다.
아래 과정은 OCI Vault을 Secret Store로 사용하기 위해 OCI Secrets Store CSI Driver Provider를 OKE 클러스터에 설치, 구성해서 사용하는 과정입니다. OCI Secrets Store CSI Driver Provider에 대한 추가적인 항목에 대한 설명은 공식 GitHub 저장소를 참조하세요.
Vault에 Secret 생성하기
OCI Vault 만들기
-
OCI 콘솔에 로그인합니다.
-
좌측 상단 햄버거 메뉴에서 Identity & Security > Key Management > Vault로 이동합니다.
-
Create Vault를 클릭합니다.
-
이름을 입력하고 생성합니다.
마스터 키 만들기
-
생성된 Vault에서 마스터키 생성을 위해 Master encryption keys 탭으로 이동합니다.
-
Create Key를 클릭합니다.
-
마스터 키 생성 정보를 입력합니다.
- Create in Compartment: 위치할 Compartment 지정
- Protection Mode: HSM(Hardware Security Module) 또는 Software 중에 선택, Software는 무료
- Name: 원하는 이름 입력
- Key Shape Algorithm: Secret 생성시 사용할 것이므로 대칭키인 AES를 선택합니다.
-
Create Key를 클릭하여 키를 생성합니다.
암호를 저장할 Secret 만들기
-
Secret 생성을 위해, 좌측 상단 햄버거 메뉴에서 Identity & Security > Secret Management로 이동합니다.
-
Applied filters에서 사용할 Vault가 맞는지 확인합니다.
-
Create Secret을 클릭합니다.
-
Secret 생성 정보를 입력합니다.
- Name: 이름을 입력합니다. 이후 OKE 클러스터에 설치될 Secrets Store Provider에서 이 이름으로 조회하게 됩니다.
- Encryption Key: 앞서 생성한 마스터키 선택
- Secret Contents: 저장할 패스워드를 입력
-
Create Secret을 클릭하여 생성합니다.
-
같은 방법으로 secret2도 생성합니다.
Secrets Store CSI Driver Provider for OCI Vault 설치
-
Helm 또는 YAML로 대상 OKE 클러스터에 설치 가능합니다. 여기서는 Helm으로 설치합니다.
- Workload Identity를 사용하는 경우,
provider.oci.auth.types.workload.resourcePrincipalVersion=<version>,provider.oci.auth.types.workload.resourcePrincipalRegion=<region>은 필요한 경우 변경합니다. 기본값은 각각 2.2, us-ashburn-1 입니다. 여기서는 OKE 클러스터가 위치한 리전으로 지정합니다.
helm repo add oci-provider https://oracle.github.io/oci-secrets-store-csi-driver-provider/charts helm install oci-provider oci-provider/oci-secrets-store-csi-driver-provider --namespace kube-system \ --set provider.oci.auth.types.workload.resourcePrincipalVersion="2.2" \ --set provider.oci.auth.types.workload.resourcePrincipalRegion="ap-chuncheon-1"- 실행예시
$ helm repo add oci-provider https://oracle.github.io/oci-secrets-store-csi-driver-provider/charts "oci-provider" has been added to your repositories $ helm install oci-provider oci-provider/oci-secrets-store-csi-driver-provider --namespace kube-system \ > --set provider.oci.auth.types.workload.resourcePrincipalVersion="2.2" \ > --set provider.oci.auth.types.workload.resourcePrincipalRegion="ap-chuncheon-1" NAME: oci-provider LAST DEPLOYED: Wed Apr 15 11:54:31 2026 NAMESPACE: kube-system STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: The Secrets Store CSI Driver and the provider are getting deployed to your cluster. Verify DaemonSets readiness: kubectl get daemonset \ --namespace kube-system \ --selector='app.kubernetes.io/name in (oci-secrets-store-csi-driver-provider, secrets-store-csi-driver)' $ kubectl get daemonset \ > --namespace kube-system \ > --selector='app.kubernetes.io/name in (oci-secrets-store-csi-driver-provider, secrets-store-csi-driver)' NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE oci-provider-secrets-store-csi-driver 3 3 3 3 3 kubernetes.io/os=linux 96s oci-secrets-store-csi-driver-provider 3 3 3 3 3 <none> 96s - Workload Identity를 사용하는 경우,
-
설정값이 잘 반영되었는지 확인합니다.
$ kubectl describe ds oci-secrets-store-csi-driver-provider -n kube-system | grep OCI_ OCI_RESOURCE_PRINCIPAL_VERSION: 2.2 OCI_RESOURCE_PRINCIPAL_REGION: ap-chuncheon-1
#1. Workload Identity를 사용하는 경우
Enhanced OKE 클러스터에서만 사용할 수 있는 기능으로 Resource Principal을 사용하여, 클러스터내에 특정 자원에 권한을 부여하는 방법입니다.
Policy 설정
참고로, 아래 내용은 개인적인 선호도에 따라 allow any-user를 사용하지 않고, Dynamic Group을 사용하였습니다. GettingStarted.md에서 처럼 allow any-user를 사용해도 동일하게 동작합니다.
-
OCI 콘솔에서 Identity Domains로 이동합니다.
-
Dynamic Group을 생성합니다.
- Name: 이름을 입력합니다, 예, oke-workload-type-dyn-grp
- Matching Rules: 아래와 같이 입력합니다.
ALL { resource.type='workload' } -
Policy를 생성합니다.
- Name: 이름을 입력합니다, 예, oke-workload-identity-to-vault-policy
- Description: Access Policies for Workloads - Secrets Store CSI Driver Provider for OCI Vault
- Compartment: OCI Vault가 위치한 Compartment 선택
- Policy: 만든 Dynamic Group 이름, OCI Secret이 있는 compartment-name, OKE 클러스터의 OCID로 각각 지정합니다.
allow dynamic-group <dynamic-group-name> to use secret-family in compartment <compartment-name> where ALL {request.principal.namespace ='<namespace>', request.principal.service_account = '<service-account>', request.principal.cluster_id = 'ocid1.cluster.oc1....'}- 설정 예시
<namespace>: OCI Vault를 실제 사용하는 앱이 위치할 namespace입니다. 여기서는 default<service-account>: OCI Vault를 실제 사용하는 앱이 위치할 namespace에서 사용할 service account입니다.
allow dynamic-group oke-workload-type-dyn-grp to use secret-family in compartment oci-hol where ALL {request.principal.namespace ='default', request.principal.service_account = 'workload-identity-sa', request.principal.cluster_id = 'ocid1.cluster.oc1.ap-chuncheon-1....'}
Workload Identity용 Service Account 생성
-
배포될 앱이 위치하는 namespace에 쿠버네티스 Service Account를 생성합니다.
kubectl create serviceaccount <service-account-name> --namespace <namespace-name>-
실행 예시
$ kubectl create serviceaccount workload-identity-sa --namespace default serviceaccount/workload-identity-sa created
-
SecretProviderClass 설정
-
배포될 앱이 사용할 OCI Vault, Secret 정보를 설정할 SecretProviderClass 생성을 위한 파일을 생성합니다.
- 파일이름 예, secret-provider-class.yaml
- 대상 OCI Vault에 있는 secret1과 secret2을 가져옵니다. secret2는 컨테이너에 app1-db-password 파일이름으로 이름을 변경해 마운트해 사용하는 예시입니다. vaultId는 앞서 만든 사용할 OCI Vault의 OCID로 지정합니다.
apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: test-oci-provider-class spec: provider: oci parameters: # provider-specific parameters secrets: | - name: secret1 # Name of the secret in vault - name: secret2 fileName: app1-db-password # Secret will be mounted with this name instead of secret name authType: workload # # possible values are: user, instance, workload vaultId: ocid1.vault.oc1..aaaaaaaaaaaaaaa -
작성한 파일을 배포합니다.
kubectl apply -f secret-provider-class.yaml -n default -
배포를 확인합니다.
$ kubectl get SecretProviderClass NAME AGE test-oci-provider-class 83s
테스트 앱 배포
-
테스트할 앱(app.deployment.yaml)을 작성합니다.
-
serviceAccountName: 앞서 만든 Service Account의 이름을 지정합니다. -
automountServiceAccountToken: true 로 설정
apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: serviceAccountName: workload-identity-sa automountServiceAccountToken: true containers: - name: nginx image: docker.io/nginx:1.21.4-alpine ports: - containerPort: 80 resources: limits: memory: 128Mi cpu: 200m volumeMounts: - name: 'some-creds' mountPath: '/mnt/secrets-store' # here are mounted secrets readOnly: true volumes: - name: some-creds csi: driver: 'secrets-store.csi.k8s.io' readOnly: true volumeAttributes: secretProviderClass: 'test-oci-provider-class' # here we reference particular SecretProviderClass -
-
해당 YAML 파일을 보면, 작성한 SecretProviderClass을 아래와 같이 volume으로 추가하고 있습니다.
volumes: - name: some-creds csi: driver: 'secrets-store.csi.k8s.io' readOnly: true volumeAttributes: secretProviderClass: 'test-oci-provider-class' # here we reference particular SecretProviderClass -
그리고 컨테이너 Pod의 /mnt/secrets-store 경로에 volume을 마운트하고 있습니다.
volumeMounts: - name: 'some-creds' mountPath: '/mnt/secrets-store' # here are mounted secrets readOnly: true -
작성한 yaml파일로 테스트 앱을 배포합니다.
kubectl apply -f app.deployment.yaml -n default -
마운트가 성공하면, Pod가 RUNNING 상태가 됩니다. 실패시 컨테이너 로그를 확인해 봅니다.
$ kubectl get sa,SecretProviderClass,pod -n default NAME SECRETS AGE serviceaccount/default 0 23d serviceaccount/workload-identity-sa 0 35s NAME AGE secretproviderclass.secrets-store.csi.x-k8s.io/test-oci-provider-class 28s NAME READY STATUS RESTARTS AGE pod/nginx-797985f5f7-khs59 1/1 Running 0 14s -
다음 명령으로 컨테이너에 Secret이 잘 마운트 되었는지 확인합니다.
kubectl exec -it deployment.apps/nginx -- sh; ls /mnt/secrets-store/; cat /mnt/secrets-store/secret1; echo cat /mnt/secrets-store/app1-db-password; echo exit;- 실행예시
- 대상 위치에서 파일로 확인할 수 있으며, 두번째 secret은 이름이 변경되어 마운트된 것을 알 수 있습니다.
$ kubectl exec -it deployment.apps/nginx -- sh; / # ls /mnt/secrets-store/; app1-db-password secret1 / # cat /mnt/secrets-store/secret1; echo test-password-1 / # cat /mnt/secrets-store/app1-db-password; echo test-password-2 / # exit; $ - 실행예시
-
정상동작하지 않는 경우 로그를 확인합니다.
kubectl logs -f -l app.kubernetes.io/name=oci-secrets-store-csi-driver-provider -n kube-system kubectl logs -f -l app.kubernetes.io/name=secrets-store-csi-driver -n kube-system
#2. Instance Principal을 사용하는 경우
OKE 클러스터의 Worker Node인 Compute 인스턴스에 사용할 OCI Vault에 접근 권한을 부여하는 방법입니다.
Policy 설정
-
OCI 콘솔로 이동합니다.
-
Dynamic Group을 생성합니다.
- Name: 이름을 입력합니다, 예, oke-worker-nodes-dynamic-group
- Matching Rules: Worker Node가 속한 Compartment의 ID로 변경하여 생성합니다.
Any {instance.compartment.id = 'ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'} -
Policy를 생성합니다.
- Name: 이름을 입력합니다, 예, oke-worker-nodes-to-vault-policy
- Policy: 만든 Dynamic Group 이름, OCI Secret이 있는 compartment-name, 사용할 OCI Vault OCID로 변경하여 생성합니다.
allow dynamic-group <dynamic-group-name> to use secret-family in compartment <compartment-name> where target.vault.id = 'ocid1.vault.oc1..aaaaaaaaaaaaaaa'
SecretProviderClass 설정
-
SecretProviderClass 생성을 위한 파일을 생성합니다.
- 파일이름 예, secret-provider-class.yaml
- 대상 OCI Vault에 있는 secret1과 secret2을 가져오고, secret2는 컨테이너에 app1-db-password 파일이름으로 마운트해 사용하는 예시입니다.
apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: test-oci-provider-class spec: provider: oci parameters: # provider-specific parameters secrets: | - name: secret1 # Name of the secret in vault - name: secret2 fileName: app1-db-password # Secret will be mounted with this name instead of secret name authType: instance # possible values are: user, instance vaultId: ocid1.vault.oc1..aaaaaaaaaaaaaaa -
작성한 파일을 배포합니다.
kubectl apply -f secret-provider-class.yaml
테스트 앱 배포
-
테스트할 앱을 다운로드 받습니다.
https://raw.githubusercontent.com/oracle/oci-secrets-store-csi-driver-provider/refs/heads/main/deploy/example/app.deployment.yaml -
해당 YAML 파일을 보면, 작성한 SecretProviderClass을 아래와 같이 volume으로 추가하고 있습니다.
volumes: - name: some-creds csi: driver: 'secrets-store.csi.k8s.io' readOnly: true volumeAttributes: secretProviderClass: 'test-oci-provider-class' # here we reference particular SecretProviderClass -
그리고 컨테이너 Pod의 /mnt/secrets-store 경로에 마운트하고 있습니다.
volumeMounts: - name: 'some-creds' mountPath: '/mnt/secrets-store' # here are mounted secrets readOnly: true -
다운로드 받은 테스트 앱을 배포합니다.
kubectl apply -f app.deployment.yaml -
마운트가 성공하면, Pod가 RUNNING 상태가 됩니다. 실패시 권한 및 OCI Vault에 Secret이 있는지 등을 확인합니다.
$ kubectl get pod NAME READY STATUS RESTARTS AGE nginx-5fd7f7876d-vtpgp 1/1 Running 0 23m -
다음 명령으로 컨테이너에 Secret이 잘 마운트 되었는지 확인합니다.
kubectl exec -it deployment.apps/nginx -- sh; ls /mnt/secrets-store/; cat /mnt/secrets-store/secret1; echo cat /mnt/secrets-store/app1-db-password; echo exit;- 실행예시
- 대상 위치에서 파일로 확인할 수 있으며, 두번째 secret은 이름이 변경되어 마운트된 것을 알 수 있습니다.
$ kubectl exec -it deployment.apps/nginx -- sh; / # ls /mnt/secrets-store/; app1-db-password secret1 / # cat /mnt/secrets-store/secret1; echo test-password-1 / # cat /mnt/secrets-store/app1-db-password; echo test-password-2 / # exit; $ - 실행예시
#3. User Principal을 사용하는 경우
User 인증을 통해서 OCI Vault에 접근 권한을 부여하는 방법입니다. OKE 클러스터 전체단위가 아닌 부분단위로 개별 유저로 사용할 수 있겠습니다.
Policy 설정
-
OCI 콘솔로 이동합니다.
-
Policy를 생성합니다.
- Name: 이름을 입력합니다, 예, oke-user-to-vault-policy
- Policy: User OCID, OCI Secret이 있는 compartment-name, 사용할 OCI Vault OCID로 변경하여 생성합니다.
allow any-user to use secret-family in compartment <compartment-name> where all { request.user.id = 'ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', target.vault.id = 'ocid1.vault.oc1..aaaaaaaaaaaaaaa' }
OKE 클러스터에 User에 대한 Secret 만들기
-
User 인증에 사용할 User에 대한 API Key가 없는 경우에 OCI 콘솔에서 생성합니다.
- Resources > API Keys로 이동합니다.
- Add API Key를 클릭하여 API Key로 사용할 키쌍을 생성합니다.
- 미리보기로 뜨는 Configuration File Preview을 사용해 인증파일 작성에 사용합니다.
-
API Key 기반 User 인증을 위한 파일을 생성합니다,
- 파일이름 예, user-auth-config-example.yaml
auth: region: ap-chuncheon-1 tenancy: ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa user: ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa fingerprint: 12:bf:17:7b:5f:e0:7d:13:75:11:d6:39:0d:e2:84:74 -
설정 파일과 API Key - Private Key를 사용하여 Secret을 만듭니다.
- private-key: API Key 경로 입력
- namespace: OKE 클러스터상의 컨테이너가 있는 namespace
kubectl create secret generic oci-config \ --from-file=config=user-auth-config-example.yaml \ --from-file=private-key=./oci_api_key.pem \ --namespace <workload-namespace>
SecretProviderClass 설정
-
SecretProviderClass 생성을 위한 파일을 생성합니다.
- 파일이름 예, secret-provider-class.yaml
- 대상 OCI Vault에 있는 secret1과 secret2을 가져오고, secret2는 컨테이너에 app1-db-password 파일이름으로 마운트해 사용하는 예시입니다.
- authType은
user로 변경합니다. authSecretName: oci-config을 새로 추가하여 방금 생성한 Secret으로 지정합니다.
apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: test-oci-provider-class spec: provider: oci parameters: # provider-specific parameters secrets: | - name: secret1 # Name of the secret in vault - name: secret2 fileName: app1-db-password # Secret will be mounted with this name instead of secret name authType: user # possible values are: user, instance authSecretName: oci-config # required only for user authType vaultId: ocid1.vault.oc1..aaaaaaaaaaaaaaa -
작성한 파일을 배포합니다.
kubectl apply -f secret-provider-class.yaml
테스트 앱 배포
테스트 절차는 #1. Instance Principal과 동일합니다.
-
테스트할 앱을 다운로드 받습니다.
https://raw.githubusercontent.com/oracle/oci-secrets-store-csi-driver-provider/refs/heads/main/deploy/example/app.deployment.yaml -
다운로드 받은 테스트 앱을 배포합니다. 또는 이미 배포된 경우 Pod를 삭제하고 다시 시작합니다.
kubectl apply -f app.deployment.yaml kubectl delete pod -l app=nginx -
다음 명령으로 컨테이너에 Secret이 잘 마운트 되었는지 확인합니다.
kubectl exec -it deployment.apps/nginx -- sh; ls /mnt/secrets-store/; cat /mnt/secrets-store/secret1; echo cat /mnt/secrets-store/app1-db-password; echo exit;- 실행예시
- 대상 위치에서 파일로 확인할 수 있으며, 두번째 secret은 이름이 변경되어 마운트된 것을 알 수 있습니다.
$ kubectl exec -it deployment.apps/nginx -- sh; / # ls /mnt/secrets-store/; app1-db-password secret1 / # cat /mnt/secrets-store/secret1; echo test-password-1 / # cat /mnt/secrets-store/app1-db-password; echo test-password-2 / # exit; $ - 실행예시
참고
이 글은 개인으로서, 개인의 시간을 할애하여 작성된 글입니다. 글의 내용에 오류가 있을 수 있으며, 글 속의 의견은 개인적인 의견입니다.