Service Account
k8s에서는 권한을 제어하기 위해 UserAccount와 ServiceAccount를 제공한다. 이때 UserAccount는 GKE의 google 계정, EKS에서의 IAM 계정과 연결되어 있어 k8s 관리 대상이 아니다.
UserAccount는 Cluster 수준에서 사용되며, 이에 반해 ServiceAccount는 네임스페이스에서 사용된다. 또한 어떠한 pod라도 반드시 SA가 하당되어야하며, 이를 통해 SA 기반 인증/인가를 진행하고 만약 이를 할당하지 않을 시 기본 account로 할당한다.
실습
$ k create serviceaccount sample-serviceaccount
serviceaccount/sample-serviceaccount created
$ k delete sa sample-serviceaccount
serviceaccount "sample-serviceaccount" deleted
위 명령을 통해 sa를 생성/삭제할 수 있다.
또한 private repository 접근을 위해 secret을 이용한다면 다음과 같이 기존 방법을 교체할 수도 있다.
기존
...
spec:
...
template:
...
spec:
imagePullSecrets:
- name: regsecret
이후
$ k patch serviecaccount default -p '{"imagePullSecrets": [{"name": "regsecret"}]}'
#또는
$ k get serviceaccounts default -o yaml > sa.yaml
$ cat <<EOF >> sa.yaml
> imagePullSecrets:
> - name: regsecret
> EOF
위와 같이 default sa에 imagePullSecrets를 추가하여 리소스가 secret을 사용할 수 있도록 하였다.
ServiceAccount Token
k8s에서는 두 개의 sa token 종류가 있다
- long-lived token
- time bound token
long-lived token
long-lived token은 만료되지 않는다. 때문에 보안 적으로 취약하며 사용에 유의하여야한다.
k8s 1.24 버전 이전에 자동으로 생성되었던 token이 해당 유형이었으며 때문에 보안 및 확장성 문제로 인하여 제거되었다.
k8s에서 추천되지는 않지만 long-lived token은 다음 방법을 통해 생성할 수 있다.
k apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: my-long-lived-secret
annotations:
kubernetes.io/service-account.name: my-sevice-account
type: kubernetes.io/service-account-token
EOF
time bound token
1.22버전 이후, k8s는 TokenRequest API를 제공한다. 해당 API를 통해 생성된 토큰은 시간이 지난 이후 만료되며 default service accoutn와 custom-defined service accounts에 모두 적용할 수 있다.
$ kubectl create token my-time-bound-token
위 명령어를 통해 토큰을 생성할 수 있다.
다만, 실제 사용 시에는 pod launch 시 automountServiceAccountToken이 true로 설정되어있다면 자동으로 volume을 마운트한다. 이후 node에서 동작중인 kubelet agent가 해당 볼륨에 token을 마운트한다.
Token Expiration
token은 한 시간 이내에 만료된다. 다만, 많은 legacy application들이 non-expiring token과 함께 작동하므로 k8s는 —service-account-extend-token-expiration=true
를 Kube API Server에 지정할 수 있게 한다.
해당 플래그 지정 시 일시적으로 더 긴 만료 시간(365 days)을 가지게 되고 legacy token들의 사용처를 기록한다.
만약 token-expiration
플래그가 true
로 지정 시 k8s는 한 시간 이내, 또는 원할 때에 만료시킬 수 있는 토큰을 만들거나 마운트할 수 있도록한다.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /var/run/secrets/tokens
name: my-proj-vol
serviceAccountName: my-service-account #service acount
volumes:
- name: my-proj-vol
projected:
sources:
- serviceAccountToken:
path: my-proj-vol
expirationSeconds: 3600 #specify the desired epiration time in seconds
Pod에 ServiceAccount 할당
default sa와 달리 custom sa는 pod에 자동으로 할당되지 않는다. 때문에 다음과 같이 구성하여야한다.
apiVersion: v1
kind: Pod
metadata:
name: sample-serviceaccount-noautomount-pod
namespace: default
spec:
serviceAccountName: sample-serviceaccount-noautomount
automountServiceAccountToken: true
containers:
- name: nginx-container
image: nginx:1.16
위와 같이 automountServiceAccountToken을 통해 자동으로 토큰 마운트를 설정할 수 있다.(비활성화를 위해선 false로 지정한다.)
apiVersion: v1
kind: ServiceAccount
metadata:
name: sample-serviceaccount-noautomount
namespace: default
automountServiceAccountToken: false
위와 같이 sa에 설정할 수도 있으며, 이렇게 되면 화이트리스트 방식으로 운영된다.(true 시에는 블랙 리스트)
Docker registry 인증 정보 설정
apiVersion: v1
kind: ServiceAccount
metadata:
name: sample-serviceaccount-pullsecret
imagePullSecrets:
- name: sample-registry-auth
위와 같이 sa에 imagePullSecrets 설정을 하게되면 할당한 파드에서 사용할 수 있다.
$ kgp sample-serviceaccount-pullsecret-pod -o yaml
...
imagePullSecrets:
- name: sample-registry-auth
...
RBAC
RBAC는 어떠한 조작을 허용할지 설정하는 Role을 SA에 연결하여 권한을 부여한다. 또한 AggregationRule을 사용하여 여러 롤을 집약할 수도 있다.
Role과 RoleBinding에는 namespace 수준의 resource와 Cluster 수준의 resource가 존재하며 세부적으로는 namespace 수준에서는 Role과 RoleBinding, cluster 수준에서는 ClusterRole과 ClusterRoleBinding이 존재한다.
기존에는 ABAC도 있었으나 현재는 RBAC를 권장한다.
Role && ClusterRole
role과 clusterRole은 둘 다 namespace 범위의 리소스를 대상으로 인가 설정을 할 수 있으며, clusterRole은 node/namespace/persistenceVolume과 같은 cluster 범위의 resource나 /version또는 /healthz와 같은 k8s API 정보를 가져오는 nonResourceURL에 대한 권한도 설정할 수 있다.
Role, ClusterRole 생성 시 주의사항
- deployment resource에 대해 롤 기술 시 deployment는 extensions/v1beta1, extensions/v1beta2, apps/v1으로 apiGroup이 변화해왔다. 때문에 role 작성 시 주의하여야한다.
- deployment resouce와 deployment scale resource는 개별적으로 지정해야한다. deployment/scale 미지정 시 레플리카 수를 변경하는 스케일 처리를 할 수 없다.
Aggregated Clusterrole
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sub-clusterrole1
labels:
app: sample-rbac
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sub-clusterrole2
labels:
app: sample-rbac
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sample-aggregated-clusterrole
aggregationRule:
clusterRoleSelectors:
- matchLabels:
app: sample-rbac
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"]
위 yaml 내용에 따라, ClusterRole 생성 시 aggragationRule을 추가하여 selector와 일치하는 cluster role을 자동으로 집계해 적용시킬 수 있다. 이때, 집계하는 role은 집계되는 영역의 role이 생성 이후에 변경된다 할지라도 지속적으로 변경을 적용한다.
k8s가 생성하는 clusterrole
k8s은 몇 개의 clusterrole을 프리셋으로 제공한다.
- cluster-admin: 모든 리소스 관리 가능
- admin: 클러스터롤 편집 + namespace 수준의 RBAC
- edit: 읽기 쓰기
- view: 읽기 전용
(시스템 구성 요소에 대한 role은 system:으로 시작한다.(ex. system:controller:clusterrole-aggregation-controller)
RoleBinding, ClusterroleBinding
rolebinding: 사용자에 대해 특정 네임스페이스에서 role 또는 clusterRole에 정의한 권한을 부여한다.
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: sample-rolebinding namespace: default roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: sample-role subjects: - kind: ServiceAccount name: sample-serviceaccount namespace: default
clusterRoleBinding: cluster 수준의 role binding
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: sample-clusterrolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: sample-clusterrole subjects: - kind: ServiceAccount name: sample-serviceaccount namespace: default
보안 Context
각 container에 대한 보안 설정이다.
privileged
apiVersion: v1 kind: Pod metadata: name: sample-privileged spec: containers: - name: nginx-container image: nginx:1.16 securityContext: privileged: true
- 특수 권한을 가진 Container로 실행
- spec.containers[].securityContext.privileged가 true일 시 Container 내부에서 기동하는 프로세스의 linux capabilities가 호스트와 동일한 권한을 가진다.
capabilities
apiVersion: v1 kind: Pod metadata: name: sample-capabilities spec: containers: - name: tools-container image: amsy810/tools:v2.0 securityContext: capabilities: add: ["SYS_ADMIN"] drop: ["AUDIT_WRITE"]
Capabilities의 추가와 삭제
$ k exec -it sample-capabilities -- capsh --print | grep Current Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_admin,cap_mknod,cap_setfcap+ep
allowPrivilegeEscalation
- Container 실행 시 상위 프로세스보다 많은 권한을 부여할지 여부
readOnlyRootFilesystem
apiVersion: v1 kind: Pod metadata: name: sample-rootfile-readonly spec: containers: - name: tools-container image: amsy810/tools:v2.0 securityContext: readOnlyRootFilesystem: true
root 파일 시스템을 읽기 전용으로 할지 여부
$ k exec -it sample-rootfile-readonly -- touch /var/test touch: cannot touch '/var/test': Read-only file system command terminated with exit code 1
runAsUser
apiVersion: v1 kind: Pod metadata: name: sample-runuser spec: securityContext: runAsUser: 65534 runAsGroup: 65534 supplementalGroups: - 1001 - 1002 containers: - name: tools-container image: amsy810/tools:v2.0
실행 사용자를 지정한다. 위 매니페스트에서는 nobody로 지정한다.
$ k exec -it sample-runuser -- id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup),1001,1002
runAsGroup: 실행 그룹
runAsNonRoot
apiVersion: v1 kind: Pod metadata: name: sample-nonroot spec: securityContext: runAsNonRoot: true containers: - name: nginx-container image: nginx:1.16
- root에서 실행 거부
seLinuxOptions: SELinux 옵션
File system group
일반적으로 mount 시 해당 volume fs의 권한은 root:root로 설정되어있다. 때문에 위의 runAsNonRoot, 또는 runAsUser 사용 시 이를 변경해야한다.
apiVersion: v1
kind: Pod
metadata:
name: sample-fsgroup
spec:
securityContext:
fsGroup: 1001
containers:
- image: nginx:1.16
name: nginx-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
$ k exec -it sample-fsgroup -- ls -ld /cache
drwxrwsrwx 2 root 1001 6 Aug 10 00:32 /cache
pod 내부 커널 파라미터 제어
k8s에서는 하나의 pod 내의 containers가 공유하는 커널 파라미터를 제어할 수 있다. 다만 커널 파라미터는 unsafe와 safe로 나뉘는데, unsafe 파라미터 제어 시 클러스터 제공자가 명시적으로 허용하지 않으면 파드 기동에 실패한다. 뿐만 아니라 k8s node 측에서 시스템 구성 요소의 기동 옵션(kubelet)에서 명시적으로 지정해야하므로 관리형 서비스에서 이용하기 어렵다.
apiVersion: v1
kind: Pod
metadata:
name: sample-sysctl
spec:
securityContext:
sysctls:
- name: net.core.somaxconn
value: "12345"
containers:
- name: tools-container
image: amsy810/tools:v2.0
$ kgp
sample-sysctl 0/1 SysctlForbidden 0 5s
$ k describe pod sample-sysctl
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning SysctlForbidden 11s kubelet forbidden sysctl: "net.core.somaxconn" not allowlisted
...
다만, privileged 옵션 true로 설정한 init container를 활용하여 unsafe한 커널 파라미터를 강제적으로 변경할 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: sample-sysctl-initcontainer
spec:
initContainers:
- name: initialize-sysctl
image: busybox:1.27
command:
- /bin/sh
- -c
- |
sysctl -w net.core.somaxconn=12345
securityContext:
privileged: true
containers:
- name: tools-container
image: amsy810/tools:v2.0
$ k exec -it sample-sysctl-initcontainer -- bash
Defaulted container "tools-container" out of: tools-container, initialize-sysctl (init)
root@sample-sysctl-initcontainer:/# sysctl net.core.somaxconn
net.core.somaxconn = 12345
PSA(Pod Security Admission) & PSS(Pod Security Standards)
기존에는 보안 정책 설정을 위해 pod security policy를 사용하였으나, 직관적이지 않은 방식으로 인해 관리자의 의도와는 다른 결함을 포함하고 있거나 적절하게 보호되지 않는 상태로 리소스를 운영하게 되는 경우가 발생함에따라 1.25부터는 deprecated 되었다.
이후 PSP는 PSA로 교체되었고 PSA는 Adminssion Controller로서 PSS에 정의된 보안 통제 항목을 구현하는 데에 사용되며 파드가 생성되기 전 파드의 보안 설정을 평가하고 이에 대한 조치를 구성할 수 있다.
PSS는 리소스에 대한 보안 정책을 정의하며 이러한 보안 정책에는 3개 분류의 세부 항목이 존재한다.
- Privileged
- 전적으로 제한이 없다.
- 권한이 있고 신뢰있는 사용자가 관리하는 시스템 및 인프라 수준의 워크로드를 대상으로 한다.
- Baseline
- baseline은 권한 상승을 방지하면서 일반적인 container 워크로드에 대해 정책 채택을 쉽게 하는 것을 목표로 한다.
- Restricted
- 일부 호환성을 희생하면서 파드 보안 강화를 위한 모범 사례를 따른다.
PSA는 다음 3가지 운영 모드를 통해 PSS 정책에 정의되어 있는 통제 항목들을 구현한다.
- enforce
- 정책 위반 시 pod는 reject 처리된다.
- audit
- 정책 위반 시 audit log에 이벤트를 기록하기 위한 감사 정보가 추가되어 기록되지만 pod 자체는 허용된다.
- warn
- 정책을 위반하게 되면 사용자에게 경고를 표시하지만 pod 자체는 허용된다.
Network policy
cluster 내부에서 pod 간 통신할 경우 트래픽 롤을 규정하는 것이다. network policy를 사용하지 않을 경우 cluster 내부의 모든 pod는 서로 통신이 가능하다.
Network policy 활성화
networki policy 활성화를 위해선 이를 지원하는 CNI를 사용해야한다. EKS에서 기본적으로 사용하는 CNI는 aws CNI이므로 다른 CNI로 교체해야한다.
network policy는 ingress와 egress로 이루어져있으며 이를 podSelector를 통해 트래픽 롤을 통해 제어한다. 또한 network policy는 namespace별로 생성해야한다.
예제
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: cloud-networkpolicy
spec:
podSelector: {}
egress:
- {}
policyTypes:
- Ingress
- Egress
위 manifest는 egress의 경우 모두 혀용하고 ingress를 막는다.
위와 같이 기본 설정 구성 후 다른 NetworkPolicy를 추가하여 화이트박스 보안 설정을 구성할 수 있다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: cloud-inbound-allow80
spec:
podSelector:
matchLabels:
app: dev
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 80
Admission Control
k8s에서의 인증은 Authentication → Authorization → Admission Control으로 구성되어있다. Authentication과 Authorization을 받은 사용자 중에서도 별도로 그 요청을 허가할지 판단하거나 리소스를 변경하여 등록할 수 있다.
Admission Control에선 다음 플러그인을 사용할 수 있다.
- NamespaceLifeCycle
- LimitRanger
- ServiceAccount
- DefaultStorageClass
- DefaultTolerationSeconds
- MutatingAdmissionWebhook
- ValidatingAdmissionWebhook
- ResourceQuota
- PodPreset
- PersistentVolumeClaimResize
- PodSecurityPolicy
위 플러그인을 통해 API 요청에 대한 기본적인 제어/수정이 이루어진다.
Pod Preset - 사라진 기능
pod가 시작되기 전에 환경변수나 스토리지 리소스의 기본값을 추가하여 특정 레이블을 가지는 pod에 대해 적용시키는 기능이었다. 다만 해당 기능은 alpha 단계에서 폐기되었다.
'DevOps > k8s' 카테고리의 다른 글
EKS VPC CNI (0) | 2023.08.28 |
---|---|
k8s dns ndots (0) | 2023.08.15 |
k8s SealedSecret 사용법 (0) | 2023.06.23 |
느슨한 결합을 위한 ExternalName 서비스 (0) | 2023.06.21 |
K8S server-side apply & managed fields (0) | 2023.06.18 |