Kuberntes OIDC -- Keycloak

Posted by hujin on January 15, 2021

架构图

keyloak.png

OIDC是一种 OAuth2 认证方式, 被某些 OAuth2 提供者支持,例如 Azure 活动目录、Salesforce 和 Google。 协议对 OAuth2 的主要扩充体现在有一个附加字段会和访问令牌一起返回, 这一字段称作 ID Token(ID 令牌)。 ID 令牌是一种由服务器签名的 JSON Web 令牌(JWT)

  • 登录到你的身份服务(Identity Provider)
  • 你的身份服务将为你提供 access_token、id_token 和 refresh_token
  • 在使用 kubectl 时,将 id_token 设置为 –token 标志值,或者将其直接添加到 kubeconfig 中
  • kubectl 将你的 id_token 放到一个称作 Authorization 的头部,发送给 API 服务器
  • API 服务器将负责通过检查配置中引用的证书来确认 JWT 的签名是合法的
  • 检查确认 id_token 尚未过期 x
  • 确认用户有权限执行操作
  • 鉴权成功之后,API 服务器向 kubectl 返回响应
  • kubectl 向用户提供反馈信息

部署keycloak

准备

1
2
3
4
5
6
# 安装java
yum install java-11-openjdk
# 获取源码包
wget https://github.com/keycloak/keycloak/releases/download/12.0.1/keycloak-12.0.1.tar.gz
tar xzvf keycloak-12.0.1.tar.gz
cd keycloak-12.0.1

启用admin用户

1
sh bin/add-user-keycloak.sh --user admin

创建证书,注意修改ip地址,证书创建到/root/ssl目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
vim /root/makessl.sh

#!/bin/bash

mkdir -p ssl

cat << EOF > ssl/ca.cnf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name

[req_distinguished_name]

[ v3_req ]
basicConstraints = CA:TRUE
EOF

cat << EOF > ssl/req.cnf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name

[req_distinguished_name]

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[alt_names]
IP.1 = 172.16.41.106   # 修改成当前机器的外网IP,否则会报错误:x509: cannot validate certificate for 172.16.41.106 because it doesn't contain any IP SANs
EOF

openssl genrsa -out ssl/ca-key.pem 2048
openssl req -x509 -new -nodes -key ssl/ca-key.pem -days 365 -out ssl/ca.pem -subj "/CN=keycloak-ca" -extensions v3_req -config ssl/ca.cnf

openssl genrsa -out ssl/keycloak.pem 2048
openssl req -new -key ssl/keycloak.pem -out ssl/keycloak-csr.pem -subj "/CN=keycloak" -config ssl/req.cnf
openssl x509 -req -in ssl/keycloak-csr.pem -CA ssl/ca.pem -CAkey ssl/ca-key.pem -CAcreateserial -out ssl/keycloak.crt -days 365 -extensions v3_req -extfile ssl/req.cnf

导入证书(密码尽量保持一致):

1
2
3
cd ssl
openssl pkcs12 -export -out keycloak.p12 -inkey keycloak.pem -in keycloak.crt -certfile ca.pem
keytool -importkeystore -deststorepass 'passw0rd' -destkeystore keycloak.jks -srckeystore keycloak.p12 -srcstoretype PKCS12

将证书拷贝至configuration目录

1
cp keycloak.jks ../keycloak-12.0.1/standalone/configuration

检查证书

1
2
3
# 确认证书的Alias Name,会在下面的standalone.xml中引用
cd ../keycloak-12.0.1/standalone/configuration
keytool -list -keystore keycloak.jks -v

修改keycloak配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vim /root/keycloak-12.0.1/standalone/configuration/standalone.xml

<management>
<management>
<security-realms>
    <security-realm name="ApplicationRealm">
        <server-identities>
         <ssl>
            <keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="passw0rd" alias="server" key-password="passw0rd" generate-self-signed-certificate-host="localhost"/>
          </ssl>
        </server-identities>
        ...
    </security-realm>
    <security-realm name="UndertowRealm">
        <server-identities>
            <ssl>
                <keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="passw0rd" />
            </ssl>
        </server-identities>
    </security-realm>

运行程序, 指定外网IP地址

1
sh bin/standalone.sh -Djboss.bind.address=172.16.41.106 -Djboss.bind.address.management=172.16.41.106

访问页面

1
登录:https://172.16.41.106:8443/auth/admin

初始化keycloak

创建realm kubernetes

页面菜单栏顶部有个“v”图标,点击并创建realm“Add realm”

创建client kubernetes

create realm kubernetes

secret key

注意:

  • access type要设置成confidential,否则后面无法获取secret key
  • valid redirect url随意填写,这里填的:http://localhost:32768

创建用户

创建用户

1
菜单栏选择“Users”后点击“Add user”,创建用户:theone(名字随意,保持一致即可)

设置密码

激活用户

1
2
访问https://172.16.41.106:8443/auth/admin/kubernetes/console
使用theone用户登录,登录后404 没有权限,可以忽略;登录后要求重新设置密码,还可以设置成之前的密码,没有关系

配置kubernetes

拷贝证书

1
将/root/ssl/ca.pem或者keycloak.crt证书文件,从keycloak节点拷贝到k8s master节点的/etc/kubernetes/ssl/目录下

修改kube apiserver配置

1
2
3
4
5
6
7
8
9
10
vim /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
...
- --oidc-issuer-url=https://172.16.41.106:8443/auth/realms/kubernetes
- --oidc-client-id=kubernetes
- --oidc-username-claim=preferred_username
- --oidc-username-prefix=-
- --oidc-ca-file=/etc/kubernetes/pki/ca.pem

此时kube api server会自动重启,注意查看容器日志

1
kubectl logs kube-apiserver-xxx -f -n kube-system

权限设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#cat rolebing.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: cluster-readonly
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: theone   # 替换成之前在keycloak中创建的用户名

测试脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import requests
from pprint import pprint
import json


auth_url = 'https://179.18.3.180:6443'
headers = {'Connection': 'Keep-Alive'}

def get_token():
    url = 'https://172.16.41.106:8443/auth/realms/kubernetes/protocol/openid-connect/token'
    req = requests.post(url, data={'client_id': 'kubernetes',
                                   'client_secret': 'aa3e7e64-d947-493e-93ca-d43faae6585e',
                                   'response_type': 'code token',
                                   'grant_type': 'password',
                                   'username': 'theone',
                                   'password': 'test',
                                   'scope': 'openid'},
                       verify=False)
    r = json.loads(req.content)
    pprint(r)
    return r


def list_ingress(tk):
    req_url = '%s/api/v1/pods' % auth_url
    headers['Authorization'] = 'Bearer %s' % tk
    print(req_url, headers)
    req = requests.get(req_url, headers=headers, verify=False)
    pprint(json.loads(req.content))

tokes = get_token()
list_ingress(tokes['id_token'])

参考

  • https://developer.ibm.com/zh/depmodels/cloud/articles/cl-lo-openid-connect-kubernetes-authentication/
  • https://developer.ibm.com/zh/articles/cl-lo-openid-connect-kubernetes-authentication2/