
쿠버네티스에서는 사용자의 인증/인가를 통해 아래 3단계를 통과해야 시스템에 접근할 수 있다..
쿠버네티스에는 크게 5가지 사용자 인증 방식이 존재한다.
Chap9에서 살펴본 HTTP Basic Auth 인증 방식과 마찬가지로 쿠버네티스 마스터에 사용자 인증을 받기 위해 Basic Auth를 사용할 수 있다.
그 전에, 먼저 KUBECONFIG 파일을 살펴보자. (KUBECONFIG 파일은 쿠버네티스 마스터 API 서버와 통신하기 위해 필요한 정보 - 마스터 서버 IP, 사용자 인증 정보 등 - 을 담고 있는 파일이다.)
cat $HOME/.kube/config

apiVersion: v1
clusters:
- cluster:
certificate-authority-data: (생략)
server: https://172.30.1.71:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: default
user:
password: (생략)
username: (생략)
이제 username과 password 정보를 가지고 curl 헤더로 전송해서 쿠버네티스 마스터에 접근해보자
# 헤더 없이 접속
curl -kv https://172.30.1.71:6443/api
# ...생략
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/api\"",
"reason": "Forbidden",
"code": 403
}
# 헤더 없이 접속
curl -kv -H "Authorization: Basic $(echo -n admin:{password}... | base64)" \
https://172.30.1.71:6443/api
# ...생략
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "172.30.1.71:6443"
}
]
}
💡 k3s 로 클러스터를 구축한 경우 기본적으로 Basic Http 방식을 이용해서 인증을 처리한다.
이제 Basic Authentication에 사용자를 추가해보자
sudo vim /var/lib/rancher/k3s/server/cred/passwd
이 파일은 Basic Authentication에 사용할 사용자 정보를 정의하는 파일이다. 각 줄마다 하나의 사용자 계정을 나타내며, 형식은 다음과 같다:
사용자이름,비밀번호,사용자ID,그룹[]
예를 들어:
admin,admin123,admin,k3s:agent
위와 같이 설정하면 사용자 이름이 admin, 비밀번호가 admin123, 사용자 ID가 admin인 k3s:agent 그룹에 속한 계정이 추가된다. 이 정보는 쿠버네티스 API 서버가 Basic Auth를 통해 사용자를 인증할 때 참조하게 된다.
이제 KUBECONFIG 파일 ($HOME/.kube/config)의 user정보(password, myuser)를 수정하고 클러스터를 재시작하면 해당 유저로 접근이 가능해진다.
💡 Group 중 system:masters 그룹은 쿠버네티스에서 특별한 의미를 가지는 예약어로, 역할을 부여하지 않아도 모든 권한을 수행할 수 있는 마스터 그룹이다.
쿠버네티스에서는 사용자 인증을 위해 X.509 인증서를 사용할 수도 있다.
HTTPS 통신을 하기 위해 인증서(X.509 Certificate)를 서버측에서 제공한다. 사용자는 서버에서 전달받은 인증서를 확인하고 서버의 신원을 확인한다.
이러한 원리를 동일하게 이용하여 사용자의 신원을 확인하기 위해 사용자 측에서 서버로 사용자의 인증서를 전달할 수 있다.
이때 쿠버네티스가 인증한 사용자 인증서를 사용해야하는데, CSR 문서를 이용하여 쿠버네티스가 보유한 CA(Certificate Authority)로 부터 서명을 받아야 한다.
💡 용어 정리
CA : 서명된 인증서를 발급하는 기관을 의미한다.
CSR(Certificate Signing Request) : 인증서 서명 요청으로 쿠버네티스가 보유한 CA로부터 인증서 서명을 받기위해 요청하는 문서이다.
사용자 인증서를 만들기 위해 cloudflare에서 만든 cfssl툴을 설치한다.
# cfssl 설치
sudo curl -L -o /usr/local/bin/cfssl https://github.com/cloudflare/cfssl/releases/latest/download/cfssl-linux-amd64
sudo chmod +x /usr/local/bin/cfssl
# cfssljson 설치
sudo curl -L -o /usr/local/bin/cfssljson https://github.com/cloudflare/cfssl/releases/latest/download/cfssljson-linux-amd64
sudo chmod +x /usr/local/bin/cfssljson
인증서 생성 방법은 다음과 같다.
이제 cfssl 툴에서 사용하는 json 형식의 CSR 파일을 생성하자.
cat > client-cert-csr.json <<EOF
{
"CN": "client-cert",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"O": "system:masters"
}
]
}
EOF
이제 생성한 CSR 파일을 이용해서 CA에 서명 요청을 한다. k3s의 경우 CA는 다음 위치에 존재한다.
사용자 인증서를 발급하기 위한 CA config 파일을 생성한다.
cat > rootCA-config.json <<EOF
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"root-ca": {
"usages": ["signing", "key encipherment", "client auth"],
"expiry": "8760h"
}
}
}
}
EOF
그리고 다음 명령을 이용해서 인증서를 발급받는다.
sudo cfssl gencert \
-ca=/var/lib/rancher/k3s/server/tls/client-ca.crt \
-ca-key=/var/lib/rancher/k3s/server/tls/client-ca.key \
-config=rootCA-config.json \
-profile=root-ca \
client-cert-csr.json | cfssljson -bare client-cert
그럼 이제 아래 파일들이 생긴것을 볼 수 있다.
CSR을 통해 쿠버네티스 CA로부터 사용자 인증서와 개인키를 발급받은 것이다.
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: (생략)
server: https://172.30.1.71:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: default
user:
client-certificate-data: (생략)
client-key-data: (생략)
위 client-certificate-data, client-key-data에 인증서와 개인키를 base64로 인코딩해서 포함하면 된다.
사용자 인증 후 권한허가 단계에서는 역할 기반 접근 제어(Role Based Access Control, RBAC)를 통해 사용자들의 권한을 관리한다.
역할기반 접근 제어는 사용자나 그룹의 역할을 기반으로 쿠버네티스의 다양한 리소스의 접근을 관리하는 방법이다.
RBAC에는 크게 3가지 리소스가 있다.
역할에는 2가지 리소스가 존재한다. 하나는 네임스페이스 안에서 역할을 정의하는 Role 리소스, 다른 하나는 네임스페이스 영역 밖에서 클러스터 레벨로 역할을 정의하는 ClusterRole 리소스.
다음은 role 리소스 에제를 살펴보자.
# role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"] # pod에 대한 권한만 설정한다.
verbs: ["get", "watch", "list"]
Subjects에는 크게 User, Group, ServiceAccount가 있다. Subjects는 Role을 부여받을 객체로, 쿠버네티스에는 User와 Group이라는 리소스가 명시적으로 구현되어있는 대신, 개념적으로만 존재한다.
예를 들어, 앞에서 배운 X.509 인증 방식에서는 인증서의 Common Name(CN) 부분이 쿠버네티스에서 User로 인식되고 Organization(O) 부분이 Group으로 인식된다.
ServiceAccount는 User나 Group과 달리 쿠버네티스에 명시적으로 구현되어 있는 리소스로 다음과 같이 입력 시 생성된 ServiceAccount 리소스를 확인할 수 있다.
kubectl get serviceaccount #sa
ServiceAccount 리소스는 네임스페이스 레벨에서 동작하며 사용자가 Pod 리소스 생성 시, 명시적으로 ServiceAccount를 지정하지 않으면 기본적으로 default Service Account가 사용된다. 따라서 모든 namespace를 생성할때에는 default Service Account가 자동으로 생성된다.
ServiceAccount의 목적은 사용자가 아닌 프로그램(Pod)이 쿠버네티스와 통신할때 사용하는 신원(Identity)이다.
ServiceAccount를 생성하는 법은 간단하다.
kubectl create sa mysa
마지막 RoleBinding 리소스는 이름에서 알 수 있듯이 Role과 Subject를 엮는 역할을 담당한다. 이흫 통해, 특정 사용자가 부여받은 특정 권한을 사용할 수 있게 한다.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: ServiceAccount
name: mysa
roleRef:
kind: Role
name: pod-viewer
apiGroup: rbac.authorization.k8s.io
위 예제는 mysa에 default 네임스페이스에 있는 모든 Pod의 view(get, list, watch) 권한을 부여하는 정의서이다.
이제 Pod에 SA를 붙여보자.
apiVersion: v1
kind: pod
metatdata:
name: nginx-sa
spec:
containers:
- image: nginx
name: nginx
# mysa 사용
serviceAccountName: mysa
serviceAccountName 생략시, default라는 이름의 sa가 설정된다.
이제 해당 pod에 k8s cli 설치 후 클러스터에 접근하면, Pod에 대한 읽기만 가능한 것을 확인할 수 있다.
k8s에는 Pod의 네트워크 접근을 제어할 수 있는 메커니즘이 존재한다. 다만, 모든 제품이 접근제어기능을 지원하지는 않고 일부 CNI(Container Network Interface)만 지원한다.
k3s 기본적으로 flannel을 네트워크 제공자로 사용한다. flannel에 Network Policy를 적용하려면 Canal을 설치해야 한다.
kubectl apply -f https://docs.projectcalico.org/manifests/canal.yaml
쿠버네티스 네트워크 기본 정책은 다음과 같다.
관리자는 네트워크를 제어하기 위해서 NetworkPolicy 리소스를 사용한다. NetworkPolicy 정책은 네임스페이스 레벨에서 동작하며 라벨 셀렉터를 이용하여 특정 Pod에 네트워크 정책을 적용한다.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- {}
egress:
- {}
예제의 네트워크 정책의 의미는 default 네임스페이스에 대해서 전체 오픈된 규칙이다. 이는 default 네임스페이스에 존재하는 모든 Pod의 인바운드, 아웃바운드 트래픽이 열려져 있다는 것을 의미한다.
책에는 네트워크 접근 제어를 활용한 다양한 예제가 있지만, 그 중에서 Private Zone을 만드는 방법과 80번 포트만 열어두는 Web 전용 네트워크 구성을 설정하는 두 개의 예제만 다뤄보겠다.
Private Zone 외부 트래픽이 들어올 수 없게 private zone을 만들어보자
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: deny-all
namespace: default
spec:
podSelector: {}
ingress: []
전체 인바운드 트래픽을 허용한 allow-all 네트워크 정책 문법과 비교했을 때,
전체을 허용하고 싶을 때는 ingress 리스트에 1개의 빈 사전({})을 선언했다.
이것은 한 개의 정책을 허용하는데, 그때 허용되는 인바운드 트래픽은 특정 조건을 가지지 않는 모든 트래픽을 뜻한다.
deny-all은 빈 리스트([])를 선언한다, 이것은 0개의 정책을 허용한다는 것을 의미하며 default 네임스페이스에 대해서 기본적으로 모든 인바운드 트래픽을 막는다.
Web pod 오픈 보통 웹 서비스는 외부 사용자들이 서비스에 접근할 수 있도록 열어둔다. 하지만 전체 트래픽이 막혀있어서 run-web 라벨이 적용된 Pod의 80포트만 허용하려 한다. 다음과 같이 Wev-open 정책을 생성한다.
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: web-open
namespace: default
spec:
podSelector:
matchLabels:
run: web
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
podSelector: {}
ports:
- protocol: TCP
port: 80