オペレータの導入と権限境界の検証
한국어 원문으로 표시합니다.
목표
오퍼레이터의 매니페스트 일습(네임스페이스, RBAC, 컨트롤러 Deployment, 리더 선출 Lease)을 올바른 순서로 설치하고, 그 권한이 실제로 의도한 경계 안에 있는지 직접 질의해 증명합니다.
왜 중요한가
오퍼레이터는 클러스터에서 가장 강한 권한을 가진 워크로드일 수 있습니다. 여러 네임스페이스의 리소스를 만들고 고치므로, 그 서비스 어카운트가 탈취되면 공격자가 그 권한을 그대로 갖습니다. 그래서 두 가지가 핵심입니다. 첫째, 설치 순서는 의존성입니다. 컨트롤러는 뜨자마자 자기 타입을 watch 하므로 CRD 가 먼저 있어야 하고, 목록 조회부터 하므로 권한이 먼저 있어야 합니다. 순서가 틀리면 크래시 루프나 403 으로 나타나는데, 원인이 코드가 아니라 배포 순서라는 것을 알아채는 데 시간이 걸립니다. 둘째, 권한은 동사를 줄이는 일입니다. verbs: ["*"] 는 편하지만, 자식 정리를 소유자 참조에 맡기면 delete 조차 필요 없는 경우가 많습니다. 그리고 RBAC 에서 webservices 와 webservices/status 는 완전히 다른 리소스입니다 — 이 사실을 모르면 "권한을 줬는데 status 를 못 쓴다"는 증상에 갇힙니다. 마지막으로 권한은 문서가 아니라 검증 대상입니다. kubectl auth can-i --as= 로 되는 것과 되면 안 되는 것을 모두 확인해 두면, 그 표가 곧 권한 경계에 대한 증거가 됩니다.
단계
시작 전 준비: 실습 파드는 실습마다 새로 뜨므로 앞 실습의 클러스터 상태는 남아 있지 않습니다. 2번과 6번이 CRD webservices.apps.labhub.io 를 요구하고 7번·8번의 권한 질의가 crd-lab 네임스페이스를 대상으로 하므로, kubectl get crd webservices.apps.labhub.io 가 비어 있으면 CRD 를 다시 적용하고 kubectl create ns crd-lab 도 하세요. CRD 는 v1alpha1 과 v1 두 버전을 가져야 하고 저장 버전은 v1 이어야 합니다(6번이 그 보존을 검사합니다).
- 네임스페이스
labhub-operator를 만들고 라벨app.kubernetes.io/part-of=labhub-operator와pod-security.kubernetes.io/enforce=restricted를 붙이세요. /root/op/install/out/install-order.txt에 설치 순서를 한 단계당 한 줄로 적으세요. 1번 줄은CustomResourceDefinition, 2번 줄은ServiceAccount와ClusterRole/ClusterRoleBinding, 3번 줄은Deployment를 언급해야 하며, 앞 줄에서Deployment나컨트롤러라는 낱말을 미리 쓰지 마세요(순서 판정이 첫 등장 줄을 기준으로 합니다). CRDwebservices.apps.labhub.io도 클러스터에 있어야 합니다.labhub-operator에 ServiceAccountwebservice-controller를 만들고, ClusterRolewebservice-controller를 다음 규칙으로 만드세요.apiGroups: ["apps.labhub.io"],resources: ["webservices"],verbs: ["get","list","watch","update","patch"]apiGroups: ["apps.labhub.io"],resources: ["webservices/status","webservices/finalizers"],verbs: ["get","update","patch"]apiGroups: [""],resources: ["events"],verbs: ["create","patch"]apiGroups: ["coordination.k8s.io"],resources: ["leases"],verbs: ["get","list","watch","create","update","patch"]동사와 리소스 어디에도*를 쓰면 안 됩니다. 그리고 ClusterRoleBindingwebservice-controller를 만들어 subject 를kind: ServiceAccount,name: webservice-controller,namespace: labhub-operator로 지정하세요.
labhub-operator에 Deploymentwebservice-controller를 만드세요.spec.replicas는 1 또는 2,spec.template.spec.serviceAccountName은webservice-controller, 컨테이너 이미지는ghcr.io/labhub/webservice-controller:v0.1.0,args에--leader-elect=true를 넣고, 컨테이너에livenessProbe와readinessProbe를 각각 두고resources.limits.memory를 설정하세요. 파드의securityContext.runAsNonRoot는true여야 합니다.labhub-operator에 Leasewebservice-controller.labhub.io를 만드세요.spec.holderIdentity는 리더의 신원 문자열,spec.leaseDurationSeconds는 5 이상,spec.renewTime은 마이크로초까지 있는 RFC3339 시각(date -u +%Y-%m-%dT%H:%M:%S.%6NZ)입니다. 그리고/root/op/install/out/lease-note.txt에 리더 선출이 없으면 왜 곤란한지(같은 오브젝트를 둘이 동시에 조정하는 문제)를 적으세요.- Deployment 의 컨테이너 이미지를
ghcr.io/labhub/webservice-controller:v0.2.0으로 올리고 롤아웃이 끝날 때까지 기다리세요. CRD 는 버전이 2개 이상 그대로 남아 있어야 하고status.storedVersions에v1이 있어야 합니다. 업그레이드 전후로 무엇을 확인했는지/root/op/install/out/upgrade-check.txt에 적되storedVersions확인 내용을 반드시 포함하세요. /opt/lab/fixtures/operator/broken-operator.yaml을/root/op/install/fixed-operator.yaml로 복사해 잘못된 세 군데를 고치고 적용하세요. 그다음/root/op/install/out/diagnosis.txt에 무엇이 왜 틀렸는지 한 줄에 하나씩 세 줄 이상 적으세요. 바인딩 주체 이름 문제와 status 서브리소스 권한 누락은 반드시 포함해야 합니다. 수정 후kubectl auth can-i update webservices/status -n crd-lab --as=system:serviceaccount:labhub-operator:webservice-controller와kubectl auth can-i list webservices --all-namespaces --as=...가 모두yes여야 합니다./root/op/install/out/permissions.json을 만드세요. 최상위 키는checks이고 각 원소는verb,resource,expected(yes/no)를 갖고 필요하면namespace를 갖습니다.namespace를 적지 않으면 전 네임스페이스 기준으로 확인됩니다. 최소 6건 이상이어야 하고,expected가no인 항목이 2건 이상이어야 하며, 그중 하나는 반드시resource가secrets여야 합니다. 예를 들어list webservices(yes),watch webservices(yes),update webservices/statusincrd-lab(yes),create eventsincrd-lab(yes),update webservices/finalizersincrd-lab(yes),get secretsincrd-lab(no),delete webservicesincrd-lab(no),create clusterrolebindings(no) 여덟 건이면 충분합니다. 모든 항목의expected가 실제 응답과 같아야 합니다.
참고
- 실습 파드는 실습마다 새로 뜨므로 앞 실습의 클러스터 상태는 남아 있지 않습니다. 그래도 선언을 파일로 남겨 두면 어느 파드에서든 같은 상태를 다시 세울 수 있습니다 — 이것이 선언형의 실질적 이점이자, 오퍼레이터 설치를 매니페스트로 관리해야 하는 이유입니다.
- 권한 확인의 기준 주체는
system:serviceaccount:labhub-operator:webservice-controller입니다.kubectl auth can-i <동사> <리소스> -n <ns> --as=<주체>형태로 씁니다. - 3번의 규칙에서
delete를 일부러 넣지 않았습니다. 자식 정리는 소유자 참조와 가비지 컬렉터에 맡기는 것이 표준이고, 그러면 침해 시 대량 삭제 경로가 막힙니다. 8번의delete webservices(no)가 이 설계를 검증합니다. - 리더 선출 Lease 는
resourceNames로 그 이름 하나에만 권한을 한정하는 것이 더 좋은 강화이지만, 이 실습에서는 요구하지 않습니다. - restricted 정책을 만족시키려면 파드에
seccompProfile.type: RuntimeDefault, 컨테이너에allowPrivilegeEscalation: false와capabilities.drop: ["ALL"]을 함께 두는 것이 좋습니다. - 흔한 실수 1: 2번에서 첫 줄에 "CRD 를 먼저 넣어야 컨트롤러가 뜬다" 처럼 뒤 단계 낱말을 미리 쓰는 것. 순서 판정이 첫 등장 줄 번호로 이루어지므로 판정이 뒤집힙니다.
- 흔한 실수 2: 3번에서
webservices에만 권한을 주고webservices/status를 빠뜨리는 것. RBAC 에서 서브리소스는 완전히 다른 리소스입니다. - 흔한 실수 3: 5번에서
renewTime을 초 단위 RFC3339 로만 쓰는 것. Lease 의 시각 필드는 마이크로초 여섯 자리를 요구합니다.
오퍼레이터 전용 네임스페이스 만들기
네임스페이스 labhub-operator 를 만들고 라벨 app.kubernetes.io/part-of=labhub-operator 와 pod-security.kubernetes.io/enforce=restricted 를 붙이세요.
구성요소를 한 번에 찾기 위한 소속 라벨과, 파드에 특권을 허용하지 않는 정책 라벨이 각각 필요합니다. 컨트롤러는 특권이 전혀 필요 없는 워크로드입니다.
설치 순서 정리하기
/root/op/install/out/install-order.txt 에 설치 순서를 한 단계당 한 줄로 적으세요. 1번 줄은 CustomResourceDefinition, 2번 줄은 ServiceAccount 와 ClusterRole/ClusterRoleBinding, 3번 줄은 Deployment 를 언급해야 하며, 앞 줄에서 Deployment 나 컨트롤러 라는 낱말을 미리 쓰지 마세요(순서 판정이 첫 등장 줄을 기준으로 합니다). CRD webservices.apps.labhub.io 도 클러스터에 있어야 합니다.
컨트롤러는 뜨자마자 자기 타입을 watch 하고 목록을 조회합니다. 그 두 가지가 먼저 준비돼 있어야 합니다. 순서 문서는 한 단계에 한 줄씩 쓰고, 앞 줄에 뒤 단계 이름을 미리 언급하지 마세요.
와일드카드 없는 최소권한 만들기
labhub-operator 에 ServiceAccount webservice-controller 를 만들고, ClusterRole webservice-controller 를 다음 규칙으로 만드세요.
apiGroups: ["apps.labhub.io"],resources: ["webservices"],verbs: ["get","list","watch","update","patch"]apiGroups: ["apps.labhub.io"],resources: ["webservices/status","webservices/finalizers"],verbs: ["get","update","patch"]apiGroups: [""],resources: ["events"],verbs: ["create","patch"]apiGroups: ["coordination.k8s.io"],resources: ["leases"],verbs: ["get","list","watch","create","update","patch"]동사와 리소스 어디에도*를 쓰면 안 됩니다. 그리고 ClusterRoleBindingwebservice-controller를 만들어 subject 를kind: ServiceAccount,name: webservice-controller,namespace: labhub-operator로 지정하세요.
메인 리소스와 서브리소스는 RBAC 에서 서로 다른 리소스입니다. status 와 finalizers 를 따로 적어야 하고, 이벤트는 코어 그룹입니다. 별표는 동사에도 리소스에도 쓰면 안 됩니다.
컨트롤러 Deployment 작성하기
labhub-operator 에 Deployment webservice-controller 를 만드세요. spec.replicas 는 1 또는 2, spec.template.spec.serviceAccountName 은 webservice-controller, 컨테이너 이미지는 ghcr.io/labhub/webservice-controller:v0.1.0, args 에 --leader-elect=true 를 넣고, 컨테이너에 livenessProbe 와 readinessProbe 를 각각 두고 resources.limits.memory 를 설정하세요. 파드의 securityContext.runAsNonRoot 는 true 여야 합니다.
기본 서비스 어카운트로 돌면 애써 만든 권한이 적용되지 않습니다. 복제본을 여럿 둘 수 있게 하는 인자와, 살아 있는지/받을 준비가 됐는지 확인하는 두 프로브, 캐시가 커질 때를 대비한 메모리 상한이 필요합니다.
리더 선출 임차 만들기
labhub-operator 에 Lease webservice-controller.labhub.io 를 만드세요. spec.holderIdentity 는 리더의 신원 문자열, spec.leaseDurationSeconds 는 5 이상, spec.renewTime 은 마이크로초까지 있는 RFC3339 시각(date -u +%Y-%m-%dT%H:%M:%S.%6NZ)입니다. 그리고 /root/op/install/out/lease-note.txt 에 리더 선출이 없으면 왜 곤란한지(같은 오브젝트를 둘이 동시에 조정하는 문제)를 적으세요.
임차에는 지금 누가 리더인지, 얼마 동안 유효한지, 마지막으로 갱신한 시각이 들어갑니다. 갱신 시각은 마이크로초까지 있는 형식이라 자릿수가 맞지 않으면 거부됩니다.
이미지 올리고 CRD 버전 보존 확인하기
Deployment 의 컨테이너 이미지를 ghcr.io/labhub/webservice-controller:v0.2.0 으로 올리고 롤아웃이 끝날 때까지 기다리세요. CRD 는 버전이 2개 이상 그대로 남아 있어야 하고 status.storedVersions 에 v1 이 있어야 합니다. 업그레이드 전후로 무엇을 확인했는지 /root/op/install/out/upgrade-check.txt 에 적되 storedVersions 확인 내용을 반드시 포함하세요.
컨트롤러 이미지는 롤링 업데이트로 올려도 되지만 CRD 의 옛 버전은 함부로 지우면 안 됩니다. 저장된 적 있는 버전 목록을 먼저 확인하세요. 롤아웃이 끝났는지도 확인해야 합니다.
고장난 오퍼레이터 매니페스트 고치기
/opt/lab/fixtures/operator/broken-operator.yaml 을 /root/op/install/fixed-operator.yaml 로 복사해 잘못된 세 군데를 고치고 적용하세요. 그다음 /root/op/install/out/diagnosis.txt 에 무엇이 왜 틀렸는지 한 줄에 하나씩 세 줄 이상 적으세요. 바인딩 주체 이름 문제와 status 서브리소스 권한 누락은 반드시 포함해야 합니다. 수정 후 kubectl auth can-i update webservices/status -n crd-lab --as=system:serviceaccount:labhub-operator:webservice-controller 와 kubectl auth can-i list webservices --all-namespaces --as=... 가 모두 yes 여야 합니다.
존재하지 않는 주체를 가리키는 바인딩은 오류 없이 만들어집니다. 그리고 메인 리소스에 대한 권한이 있어도 서브리소스는 별개입니다. 확인은 로그가 아니라 권한 질의로 하세요.
권한 경계 검증표 만들기
/root/op/install/out/permissions.json 을 만드세요. 최상위 키는 checks 이고 각 원소는 verb, resource, expected(yes/no)를 갖고 필요하면 namespace 를 갖습니다. namespace 를 적지 않으면 전 네임스페이스 기준으로 확인됩니다. 최소 6건 이상이어야 하고, expected 가 no 인 항목이 2건 이상이어야 하며, 그중 하나는 반드시 resource 가 secrets 여야 합니다. 예를 들어 list webservices(yes), watch webservices(yes), update webservices/status in crd-lab(yes), create events in crd-lab(yes), update webservices/finalizers in crd-lab(yes), get secrets in crd-lab(no), delete webservices in crd-lab(no), create clusterrolebindings(no) 여덟 건이면 충분합니다. 모든 항목의 expected 가 실제 응답과 같아야 합니다.
되는 것만 확인하면 경계를 증명할 수 없습니다. 되면 안 되는 것도 함께 넣고 실제 응답과 대조하세요. 네임스페이스를 적지 않은 항목은 전 네임스페이스 기준으로 확인됩니다.