Ssoon

Argo CD in Practice – 4) 접근 제어 : 서비스 계정 본문

CICD Study [1기]

Argo CD in Practice – 4) 접근 제어 : 서비스 계정

구구달스 2025. 10. 19. 19:15

 

🔐 Argo CD Service Accounts

  • Argo CD에서 Service Account는 자동화된 프로세스(CI/CD 파이프라인 등)가 시스템에 접근할 수 있도록 해주는 중요한 인증 수단입니다.
    이 계정은 실제 사용자의 계정과 분리되어야 하며, 최소 권한 원칙(Principle of Least Privilege)을 반드시 지켜야 합니다.

🧩 Service Account란?

  • 자동화 도구나 파이프라인이 Argo CD에 접근할 때 사용하는 계정입니다.
    이 계정은 실제 사용자 계정이 아니기 때문에, 특정 사용자가 비활성화되거나 권한이 변경되더라도 파이프라인이 중단되지 않습니다.

“Service Account는 사용자 계정과 분리되어야 하며, 필요한 권한만 부여해야 한다.”


  • Argo CD에서 Service Account를 만드는 방법은 두 가지입니다.
  1. Local User 기반 Service Account (apiKey 사용)
  2. Project Role 기반 Service Account (token 사용)

🧰 Local Service Account 만들기

  • Local Service Account는 UI나 CLI 로그인을 위한 비밀번호 없이, API Key로만 인증하는 계정입니다.
    예를 들어, 파이프라인에서 특정 Application의 Sync 작업만 수행할 수 있도록 제한할 수 있습니다.

⚙️ Step 1. argocd-cm 수정

  • 아래처럼 ConfigMap을 수정하여 새로운 로컬 계정을 추가합니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ KUBE_EDITOR="vi"  kubectl edit cm -n argocd argocd-cm
configmap/argocd-cm edited

apiVersion: v1
data:
  accounts.alice: apiKey,login
  accounts.gitops-ci: apiKey
  admin.enabled: "false"
  
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ argocd account list
NAME       ENABLED  CAPABILITIES
admin      false    login
alice      true     apiKey, login
gitops-ci  true     apiKey

“accounts.gitops-ci: apiKey 설정으로 로그인 불가능한 API 전용 계정을 만든다.”


⚙️ Step 2. Token 생성 권한 부여

  • 문제는 alice 사용자에게는 다른 계정의 토큰을 생성할 권한이 없다는 점입니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ argocd account generate-token -a gitops-ci
{"level":"fatal","msg":"rpc error: code = PermissionDenied desc = permission denied: accounts, update, gitops-ci, sub: alice, iat: 2025-11-12T09:30:57Z","time":"2025-11-12T18:58:42+09:00"}
  • admin 계정을 다시 활성화할 수도 있지만, 이는 보안상 권장되지 않습니다.
  • 이를 해결하기 위해 argocd-rbac-cm.yaml에 새로운 Role을 추가합니다.
policy.csv 실제 RBAC 정책 정의. 형식: p, <role>, <resource>, <action>, <scope>, <effect>
p, role:user-update, accounts, update, *, allow role:user-update가 accounts 리소스에 대해 update 권한을 모든 범위(*)에 허용
p, role:user-update, accounts, get, *, allow role:user-update가 accounts 리소스를 조회(get)할 수 있음
g, alice, role:user-update alice 계정에 role:user-update를 부여
policy.default: role:readonly 지정되지 않은 계정/리소스는 기본적으로 읽기 전용
policy.matchMode: glob 패턴 매칭 모드. * 같은 와일드카드 사용 가능
scopes: '[groups]' 정책 적용 범위를 그룹 단위로 설정 (여기서는 그룹 기반 적용)
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ KUBE_EDITOR="vi"  kubectl edit cm -n argocd argocd-rbac-cm
configmap/argocd-rbac-cm edited

apiVersion: v1
data:
  policy.csv: |
    p, role:user-update, accounts, update, *, allow
    p, role:user-update, accounts, get, *, allow
    g, alice, role:user-update
  policy.default: role:readonly
  policy.matchMode: glob
  scopes: '[groups]'

“alice에게 계정 업데이트 및 조회 권한(role:user-update)을 부여한다.”


⚙️ Step 3. Token 생성

  • 권한이 부여된 alice 사용자로 CLI에서 아래 명령어를 실행합니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ argocd account generate-token -a gitops-ci
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJnaXRvcHMtY2k6YXBpS2V5IiwibmJmIjoxNzYyOTQxNjMyLCJpYXQiOjE3NjI5NDE2MzIsImp0aSI6IjcwNzFhN2U1LTg1ZDQtNDU0OC1iZjI1LTZhZDhkNzYzYjIyMCJ9.K5ngFnJe-8liatLgKqv6ixgBwt2u1igx68AMyDxXxeI

⚙️ Step 4. Token 검증

  • 생성된 토큰이 잘 동작하는지 확인하기 위해 다음 명령을 실행합니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJnaXRvcHMtY2k6YXBpS2V5IiwibmJmIjoxNzYyOTQxNjMyLCJpYXQiOjE3NjI5NDE2MzIsImp0aSI6IjcwNzFhN2U1LTg1ZDQtNDU0OC1iZjI1LTZhZDhkNzYzYjIyMCJ9.K5ngFnJe-8liatLgKqv6ixgBwt2u1igx68AMyDxXxeI
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ argocd account get-user-info --auth-token $TOKEN
Logged In: true
Username: gitops-ci
Issuer: argocd
Groups:

 

“API Key 기반 계정은 UI 로그인이 불가능하며, CLI 또는 API를 통해서만 접근할 수 있다.”


⚙️ Step 5. 권한 부여

  • 현재 상태에서는 기본적으로 read-only 권한만 있습니다.
  • Service Account가 특정 Application을 sync하거나 create, delete 등의 작업을 수행하려면 argocd-rbac-cm에 추가 정책을 명시해야 합니다.

“Local Account는 group 기반 권한 설정이 불가능하며, 오직 role을 통해 권한을 부여할 수 있다.”


🧭 Project Role 기반 Service Account

  • 두 번째 방식은 AppProject의 Role과 Token을 이용하는 것입니다.
  • AppProject는 Application이 어떤 리소스, 네임스페이스, 클러스터에서 동작할 수 있는지를 제한하는 기능입니다.
  • 여기에 Role을 추가하고 Token을 발급하면, 해당 Role이 가진 권한 범위 내에서만 동작하는 Service Account를 만들 수 있습니다.
  • Argo CD가 설치되면 default 라는 기본 프로젝트가 제공되는데, 기본 프로젝트는 애플리케이션에 대한 제한 사항이 설정돼 있지 않다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl get appprojects.argoproj.io -n argocd default -o yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  creationTimestamp: "2025-11-12T08:17:25Z"
  generation: 1
  name: default
  namespace: argocd
  resourceVersion: "4561"
  uid: c1c76e2d-c341-4951-8187-9a5af07f7cad
spec:
  clusterResourceWhitelist:
  - group: '*'
    kind: '*'
  destinations:
  - namespace: '*'
    server: '*'
  sourceRepos:
  - '*'
status: {}

⚙️ Step 1. AppProject 신규 생성

  • 프로젝트를 토큰과 함께 사용하는 방법을 확인하기 위해 새 프로젝트를 만들고 기존 argocd 애플리케이션에 사용해볼 것이다.
  • 일단 새 프로젝트를 만들면 프로젝트에 사용할 역할을 생성하고 역할에 권한을 할당하고 토큰을 생성해야 한다.
p policy type (권한 부여)
proj:sample-apps:read-sync 역할 이름 지정 (위에서 만든 read-sync 역할)
applications 권한이 적용될 리소스 타입
get 허용되는 액션 (조회)
sample-apps/* 적용 범위 (이 프로젝트 내 모든 애플리케이션)
allow 허용 여부
  • clusterResourceWhitelist
    • 이 프로젝트에서 허용된 클러스터 리소스를 정의합니다.
    • group: '*', kind: '*' → 모든 그룹과 모든 종류의 클러스터 리소스 접근 허용
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: sample-apps
  namespace: argocd
spec:
  roles:
    - name: read-sync
      description: read and sync privileges
      policies:
        - p, proj:sample-apps:read-sync, applications, get, sample-apps/*, allow
        - p, proj:sample-apps:read-sync, applications, sync, sample-apps/*, allow
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
  description: Project to configure argocd self-manage application
  destinations:
    - namespace: test
      server: https://kubernetes.default.svc
  sourceRepos:
    - https://github.com/argoproj/argocd-example-apps.git
EOF
appproject.argoproj.io/sample-apps created

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ kubectl get appproject -n argocd
NAME          AGE
default       108m
sample-apps   11s

“read-sync Role은 Application에 대한 get/sync 권한만 가진다.”


⚙️ Step 2. 해당 프로젝트에 app 배포

  • 애플리케이션 생성
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: pre-post-sync
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: sample-apps
  source:
    path: pre-post-sync
    repoURL: https://github.com/argoproj/argocd-example-apps
    targetRevision: master
  destination:
    namespace: test
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      enabled: false
    syncOptions:
    - CreateNamespace=true
EOF
Warning: metadata.finalizers: "resources-finalizer.argocd.argoproj.io": prefer a domain-qualified finalizer name including a path (/) to avoid accidental conflicts with other finalizer writers
application.argoproj.io/pre-post-sync created

(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ argocd app list
NAME                  CLUSTER                         NAMESPACE  PROJECT      STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO                                             PATH            TARGET
argocd/guestbook      https://kubernetes.default.svc  guestbook  default      Synced     Healthy  Auto-Prune  <none>      https://github.com/argoproj/argocd-example-apps  helm-guestbook  HEAD
argocd/pre-post-sync  https://kubernetes.default.svc  test       sample-apps  OutOfSync  Missing  Manual      <none>      https://github.com/argoproj/argocd-example-apps  pre-post-sync   master
  • 동기화 실행 시 실패!
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ argocd app sync argocd/pre-post-sync
{"level":"fatal","msg":"rpc error: code = PermissionDenied desc = permission denied: applications, sync, sample-apps/pre-post-sync, sub: alice, iat: 2025-11-12T09:30:57Z","time":"2025-11-12T19:11:17+09:00"}


⚙️ Step 3. Role Token 생성 권한 추가

  • 이제 alice 가 Role Token을 생성할 수 있도록 RBAC 설정을 업데이트합니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ KUBE_EDITOR="vi"  kubectl edit cm -n argocd argocd-rbac-cm
configmap/argocd-rbac-cm edited

apiVersion: v1
data:
  policy.csv: |
    p, role:user-update, accounts, update, *, allow
    p, role:user-update, accounts, get, *, allow
    p, role:user-update, projects, update, sample-apps, allow
    g, alice, role:user-update

“특정 프로젝트(argocd)에 대해서만 업데이트 권한을 허용한다.”


⚙️ Step 4. Token 생성

  • 이제 Token을 생성할 수 있습니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ argocd proj role create-token sample-apps read-sync
Create token succeeded for proj:sample-apps:read-sync.
  ID: 5a26c1a3-659e-4e60-b638-05a62a4d6913
  Issued At: 2025-11-12T19:14:30+09:00
  Expires At: Never
  Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJwcm9qOnNhbXBsZS1hcHBzOnJlYWQtc3luYyIsIm5iZiI6MTc2Mjk0MjQ3MCwiaWF0IjoxNzYyOTQyNDcwLCJqdGkiOiI1YTI2YzFhMy02NTllLTRlNjAtYjYzOC0wNWE2MmE0ZDY5MTMifQ.RSWvFO_IDr8ZcwXljvq7CkIN7RAunK7qDJ4Cqi1ADIM

 


⚙️ Step 5. Token 사용

  • 이 Token으로 sync 작업을 실행할 수 있습니다.
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJwcm9qOnNhbXBsZS1hcHBzOnJlYWQtc3luYyIsIm5iZiI6MTc2Mjk0MjQ3MCwiaWF0IjoxNzYyOTQyNDcwLCJqdGkiOiI1YTI2YzFhMy02NTllLTRlNjAtYjYzOC0wNWE2MmE0ZDY5MTMifQ.RSWvFO_IDr8ZcwXljvq7CkIN7RAunK7qDJ4Cqi1ADIM
(⎈|kind-myk8s:N/A) ssoon@DESKTOP-72C919S:~$ argocd app sync argocd/pre-post-sync --auth-token $TOKEN
TIMESTAMP                  GROUP        KIND   NAMESPACE                  NAME                    STATUS    HEALTH        HOOK  MESSAGE
2025-11-12T19:15:32+09:00            Service        test  pre-post-sync-kustomize-guestbook-ui  OutOfSync  Missing  
2025-11-12T19:15:32+09:00   apps  Deployment        test  pre-post-sync-kustomize-guestbook-ui  OutOfSync  Missing  
2025-11-12T19:15:32+09:00          Namespace                              test   Running   Synced              namespace/test created
2025-11-12T19:15:32+09:00  batch         Job        test  pre-post-sync-before            Progressing
2025-11-12T19:15:34+09:00  batch         Job        test  pre-post-sync-before   Running   Synced     PreSync  job.batch/pre-post-sync-before created
2025-11-12T19:15:50+09:00            Service        test  pre-post-sync-kustomize-guestbook-ui    Synced  Healthy   
2025-11-12T19:15:50+09:00   apps  Deployment        test  pre-post-sync-kustomize-guestbook-ui    Synced  Progressing
2025-11-12T19:15:50+09:00   apps  Deployment        test  pre-post-sync-kustomize-guestbook-ui    Synced  Healthy   
2025-11-12T19:15:52+09:00  batch         Job        test  pre-post-sync-before                  Succeeded   Synced     PreSync  Reached expected number of succeeded pods
2025-11-12T19:15:52+09:00            Service        test  pre-post-sync-kustomize-guestbook-ui    Synced   Healthy              service/pre-post-sync-kustomize-guestbook-ui created
2025-11-12T19:15:52+09:00   apps  Deployment        test  pre-post-sync-kustomize-guestbook-ui    Synced   Healthy              deployment.apps/pre-post-sync-kustomize-guestbook-ui created
2025-11-12T19:15:52+09:00  batch         Job        test   pre-post-sync-after   Running   Synced    PostSync  job.batch/pre-post-sync-after created
2025-11-12T19:16:10+09:00  batch         Job        test   pre-post-sync-after  Succeeded   Synced    PostSync  Reached expected number of succeeded pods

Name:               argocd/pre-post-sync
Project:            sample-apps
Server:             https://kubernetes.default.svc
Namespace:          test
URL:                https://argocd.example.com/applications/argocd/pre-post-sync
Source:
- Repo:             https://github.com/argoproj/argocd-example-apps
  Target:           master
  Path:             pre-post-sync
SyncWindow:         Sync Allowed
Sync Policy:        Manual
Sync Status:        Synced to master (0d521c6)
Health Status:      Healthy

Operation:          Sync
Sync Revision:      0d521c6e049889134f3122eb32d7ed342f43ca0d
Phase:              Succeeded
Start:              2025-11-12 19:15:32 +0900 KST
Finished:           2025-11-12 19:16:10 +0900 KST
Duration:           38s
Message:            successfully synced (no more tasks)

GROUP  KIND        NAMESPACE  NAME                                  STATUS     HEALTH   HOOK      MESSAGE
       Namespace              test                                  Running    Synced             namespace/test created
batch  Job         test       pre-post-sync-before                  Succeeded           PreSync   Reached expected number of succeeded pods
       Service     test       pre-post-sync-kustomize-guestbook-ui  Synced     Healthy            service/pre-post-sync-kustomize-guestbook-ui created
apps   Deployment  test       pre-post-sync-kustomize-guestbook-ui  Synced     Healthy            deployment.apps/pre-post-sync-kustomize-guestbook-ui created
batch  Job         test       pre-post-sync-after                   Succeeded           PostSync  Reached expected number of succeeded pods
  • 결과로 Application의 리소스 상태가 동기화되며, UI에서도 “Sync Status” 버튼을 통해 동기화 상태를 확인할 수 있습니다.

“Project Role Token은 해당 Project의 Application에만 접근 가능하다.”


💡 Token 관리 팁

  • 모든 Token은 생성된 Project Role에 저장되며,사용 이력 및 만료 시간(expiresAt)을 설정할 수 있습니다.
    보안을 강화하기 위해 정기적인 Token Rotation을 권장합니다.

“Pipeline 실패를 방지하려면 Token 만료를 자동화하거나 주기적으로 갱신해야 한다.”


⚡️ Token을 이용한 Sync의 유용성

  • Argo CD Application에 자동 동기화(auto-sync) 기능이 있더라도,Token 기반 수동 Sync를 허용하는 것은 여전히 유용합니다.
  • 예를 들어,
    • auto-sync 주기를 10~15분으로 늘려 시스템 부하를 줄이고,
    • Git Commit 시점에 파이프라인에서 sync 명령을 직접 호출할 수 있습니다.

“자동 동기화를 유지하면서도, Pipeline에서 Token으로 수동 Sync를 트리거할 수 있다.”


📌 핵심 요약

  • Service Account는 자동화를 위한 비사용자 계정이며, 최소 권한 원칙을 적용해야 함
  • Local Account 방식은 apiKey로 인증하고, UI 로그인 불가
  • Project Role 방식은 특정 AppProject 내 Role과 Token을 이용하여 제한된 권한 부여
  • RBAC 설정은 argocd-rbac-cm에서 Casbin 정책(p, g)을 사용
  • Token Rotation과 만료 관리를 통해 보안 유지
  • Token 기반 Sync는 auto-sync와 병행하여 파이프라인 유연성을 확보

“Service Account는 자동화를 위한 핵심 구성요소이며, 보안과 최소 권한 설정이 무엇보다 중요하다.”


Comments