纯翻译,未测试,点击“阅读原文”查看原文。
本文中我们将会为 Kuebernetes 构建一个完备的单点登录系统,这个系统会为 kubectl
、Web 应用的 Ingress,以及 Docker 镜像仓库和 Gitea 提供服务,本文中会涉及多数单点登录模型,对于 Gitlab、Kibana、Grafana 等其它应用,应该也是适用的。
整个方案中使用了以 OpenLDAP 为后端的 KeyCloak 服务。OpenLDAP 能满足 Gitea 的需求,但基于 OIDC 的 kubectl 单点登录之类的功能是不需要它的。
本文中的一些亮点:
通过浏览器完成 kubectl 的单点登录;
用同样简单、但更安全的注解来替换 Ingress 的 Basic 认证注解;
使用完整的 ACL(访问控制列表)来进行 Docker 容器镜像的推送和拉取。
我们假设你能够使用 Kubectl 访问 Kubernetes 集群(集群中包含 CSI 支持),在其中创建一个名为 identity
的命名空间。能够使用 Helm 3。最后假设你使用 Nginx Ingress 控制器,并包含 Cert Manager 组件,并用 Cert Manager 为集群提供一个称为 letsencrypt-production
的 SSL 证书签发者。
如果你的配置不满足上述要求,一些主要步骤应该还是一致的,Ingress 注解可能会有一些不同。本文中涉及的源码位于 https://github.com/TalkingQuickly/kubernetes-sso-guide.git
,可以 Clone 到本地:
git clone https://github.com/TalkingQuickly/kubernetes-sso-guide.git
后续所有命令都认为你的当前目录是上述源码的根目录。
因为 stable
Chart 仓库已经淘汰,OpenLDAP 的 Chart 也受到波及。因为还没有更新,本文会使用最近的版本。
openldap/values-openldap.yml
中包含了 values 样例,我们可以做一些因地制宜的修改。
# Default configuration for openldap as environment variables. These get injected directly in the container.
# Use the env variables from https://github.com/osixia/docker-openldap#beginner-guide
env:
LDAP_ORGANISATION: "Talking Quickly Demo"
LDAP_DOMAIN: "ssotest.staging.talkingquickly.co.uk"
LDAP_BACKEND: "hdb"
LDAP_TLS: "true"
LDAP_TLS_ENFORCE: "false"
LDAP_REMOVE_CONFIG_AFTER_SETUP: "true"
LDAP_READONLY_USER: "true"
LDAP_READONLY_USER_USERNAME: readonly
LDAP_READONLY_USER_MASSWORD: password
# Default Passwords to use, stored as a secret. If unset, passwords are auto-generated.
# You can override these at install time with
# helm install openldap --set openldap.adminPassword=,openldap.configPassword=
adminPassword: admin
configPassword: 9h8sdfg9sdgfjsdfg8sdgsdfjgklsdfg8sdgfhj
customLdifFiles:
initial-ous.ldif: |-
dn: ou=People,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk
objectClass: organizationalUnit
ou: People
dn: ou=Group,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk
objectClass: organizationalUnit
ou: Group
上一节配置中,我使用 dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk
作为 Kubernetes 集群的基础域。我们的 LDAP 服务仅对内提供服务,因此不需要映射到 DNS。
customLdifFiles
的内容是用于对 LDAP 数据库进行初始化的。这里提供了两个 organizationalUnit
,类型为 People
的项目会用于保存个人信息,另外一个类型为 Group
的则会用于保存群组信息。OU
可以近似的看做是传统文件系统中的文件夹。OpenLDAP OUs 一文中详细解释了这方面的内容。
现在就可以用下面的命令来安装 OpenLDAP 了:
helm upgrade --install openldap ./charts/openldap --values openldap/values-openldap.yml
安装过程中会输出一些成功信息,以及访问该服务的示例。
注意下面几个用于获取配置和密码的命令:
kubectl get secret --namespace identity openldap -o jsonpath="{.data.LDAP_ADMIN_PASSWORD}" | base64 --decode; echo
kubectl get secret --namespace identity openldap -o jsonpath="{.data.LDAP_CONFIG_PASSWORD}" | base64 --decode; echo
我们最后会使用 KeyCloak 来管理 LDAP 目录,在那之前可以熟悉一下 OpenLDAP 客户端。Splunk 有文章介绍了这个客户端。
首先用 kubectl proxy
来开放 LDAP 服务:
kubectl port-forward --namespace identity \
$(kubectl get pods -n identity --selector='release=openldap' -o jsonpath='{.items[0].metadata.name}') \
3890:389
在另一个终端里,执行下面的命令(包括 OSX 在内的多数 Unix 系统都安装了 ldapsearch,如果没有的话,就需要进行安装,例如 Debian 发行版就需要安装 ldap-utils
):
ldapsearch -x -H ldap://localhost:3890 \
-b dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk \
-D "cn=admin,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk" \
-w password
命令会返回如下信息:
objectClass: organization
o: Talking Quickly's Demo
dc: k4stest4
# admin, k4stest4.talkingquickly.co.uk
dn: cn=admin,dc=k4stest4,dc=talkingquickly,dc=co,dc=uk
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e1NTSEF9YjM1a0hLYXVwcDlvcGU5R1N2UE5qcFBLd3FxdUorWFk=
# People, k4stest4.talkingquickly.co.uk
dn: ou=People,dc=k4stest4,dc=talkingquickly,dc=co,dc=uk
objectClass: organizationalUnit
ou: People
# Group, k4stest4.talkingquickly.co.uk
dn: ou=Group,dc=k4stest4,dc=talkingquickly,dc=co,dc=uk
objectClass: organizationalUnit
ou: Group
# search result
search: 2
result: 0 Success
# numResponses: 6
# numEntries: 4
Stack Overflow 上有一篇帖子描述了 dn
、dc
这些名词的详情。
注意我的测试域 ssotest.staging.talkingquickly.co.uk
的结果是一个 dn
,其中包含了一组逗号分隔的 dc
列表:dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk
。这里看到我们 customLdiffFiles
定义的 ou
已经建立了。
现在我们已经有了 LDAP 服务器,通过简单的测试,检查了我们保存在服务之中的数据。接下来就可以安装 KeyCloak 了。
Helm 3 提倡使用去中心化的仓库替代原有的仓库,所以在安装 Keycloak 之前要首先加入新的仓库:
helm repo add codecentric https://codecentric.github.io/helm-charts
有兴趣的话还可以看看这个 Chart 的详细信息。
为了进行一些基础配置,我们需要配置 Ingress,并启用 Postgres 存储数据。
这里假设你的集群中包含了 Ingress 和存储支持。我用域名通配符 *.ssotest.staging.talkingquickly.co.uk
指向测试集群。所以我们的 keycloak/values-keycloak
看起来是这样的:
extraEnv: |
- name: KEYCLOAK_LOGLEVEL
value: DEBUG
- name: KEYCLOAK_USER
value: admin
- name: KEYCLOAK_PASSWORD
value: as897gsdfs766dfsgjhsdf
- name: PROXY_ADDRESS_FORWARDING
value: "true"
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
rules:
- host: sso.ssotest.staging.talkingquickly.co.uk
paths:
- /
args:
- -Dkeycloak.profile.feature.docker=enabled
tls:
- hosts:
- sso.ssotest.staging.talkingquickly.co.uk
secretName: keycloak-tld-secret
postgresql:
enabled: true
postgresqlPassword: asdfaso97sadfjylfasdsf78
用如下命令执行安装:
helm upgrade --install keycloak codecentric/keycloak --values keycloak/values-keycloak.yml
在 values-keycloak.yml
中使用环境变量 KEYCLOAK_USER
以及 KEYCLOAK_PASSWORD
设置了 keycloak 的用户名和密码,我们将会用这个信息来访问 Keycloak 的控制台。
我们根据前面的 Ingress 设置来浏览控制台页面(例如 https://sso.ssotest.staging.talkingquickly.co.uk
),然后输入用户名密码完成登录。这里可以创建用户和即将使用 Keycloak 进行单点登录的应用程序。
注意登录管理控制台和服务用户登录是各自独立的。
在 Keycloak 中我们可以创建多个 realms
,代表不同的认证服务。例如我们可能要给内部系统创建一个 realm
,另外给客户创建独立的 realm
。缺省 realm
命名为 master
,根据前面的 Ingress 定义,用户可以访问 https://sso.ssotest.staging.talkingquickly.co.uk/auth/realms/master/account
进行登录。访问 https://sso.ssotest.staging.talkingquickly.co.uk/auth/realms/master
能够获取该 realm
的有用信息。
这里有一个容易混淆的环节就是使用管理员凭据而非在特定 realm
中创建的普通用户的凭据进行登录。
完成了 Keycloak 和 OpenLDAP 的部署之后,可以进行进一步的配置,把两个系统连接起来,让 Keycloak 使用 OpenLDAP 存储用户数据。
用管理用户登录到 Keycloak 控制台,进入 User Federation
从 Add Provider
下拉列表中选择 ldap
。按照如下数值填写关键字段:
Edit Mode: Writable
Sync Registrations: On
Vendor: Other
Connection URL: ldap://openldap.identity.svc.cluster.local; you’ll need to change identity to match the namespace you’re working in)
Users DN: ou=People,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk; you’ll need to change the dc entries to match your base dn. Note that here we’re telling Keycloak that users are stored in our People ou, created from the customLdiffFiles.
Authentication Type: simple
Bind DN: cn=admin,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk again, updating the dc entries to match your base dn
Bind Credentials: Set this to the admin password we used for ldapsearch earlier
填写完成后保存数据。这个配置数据和缺省数据稍有不同,需要确认 memberOf
属性是否正常工作。在这方面,Github 上有一个很长的 Issue。有些应用需要这个功能来根据分组来进行访问控制。
可以用 ldapsearch
验证 memberOf
是否正常工作,我们需要使用搜索操作符来进行搜索:
ldapsearch -x -H ldap://localhost:3890 -b dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk -D "cn=admin,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk" "+" -w password
要在本地运行这个命令,需要在本地进行流量转发:
kubectl port-forward --namespace identity \
$(kubectl get pods -n identity --selector='release=openldap' -o jsonpath='{.items[0].metadata.name}') \
3890:389
点击 Keycloak 管理门户左侧的 Users
,选择 Add User
。填写并保存用户数据之后,就可以用 ldapsearch
来检查用户是否已经成功创建。可以用端口转发的方式来进行验证:
kubectl port-forward --namespace identity \
$(kubectl get pods -n identity --selector='release=openldap' -o jsonpath='{.items[0].metadata.name}') \
3890:389
然后进行搜索:
ldapsearch -x -H ldap://localhost:3890 -b dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk -D "cn=admin,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk" "+" -w password
这里应该用真实密码代替此处的 password
。
输出内容中应该能看到如下内容:
# talkingquickly, People, ssotest.staging.talkingquickly.co.uk
dn: uid=talkingquickly1,ou=People,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk
uid: talkingquickly1
objectClass: inetOrgPerson
objectClass: organizationalPerson
mail: [email protected]
sn: Dixon
cn: Ben
这表明用户已经成功建立。接下来配置一下用户组。我们浏览 Groups
页面,加入一个 Administrator
组,重新运行 ldapsearch
命令,我们不会看到任何变化,群组没有出现。
返回 Users
页,选择或者新建一个用户,并进入 Groups
页面把用户加入群组。回到 Users
,会看到一个空列表,必须选择 View all users
才能看到用户信息。
完成这些动作之后,重新运行 ldapsearch
,会看到类似下面的内容:
# Administrators, Group, k4stest4.talkingquickly.co.uk
dn: cn=Administrators,ou=Group,dc=k4stest4,dc=talkingquickly,dc=co,dc=uk
objectClass: groupOfNames
cn: Administrators
member: cn=empty-membership-placeholder
member: uid=talkingquickly,ou=People,dc=k4stest4,dc=talkingquickly,dc=co,dc=uk
这些输出表示我们的群组已经建立,并且已经有用户成为群组的成员。
如果我们查看一下我们的用户条目,点击 +
,会看到包含一行内容:
memberOf: cn=Administrators,ou=Group,dc=ssotest,dc=staging,dc=talkingquickly,dc=co,dc=uk
说明 memberOf
功能正常。
本文不会涉及更多 ldapsearch
的内容,ldapsearch Examples 中包含了很多的详细用例。
使用 Kubernetes 集群的团队要面临的一个常见痛点就是管理连接集群的配置文件。一般的解决办法,要么是使用 KUBECONFIG 环境变量指定配置文件,其中包含了硬编码的凭据;要么就是使用自定义脚本来包装 AWS 或者 GCP 的客户端。
本节中我们会把 Kubernetes 和 Keycloak 集成起来,这样在我们执行 kubectl
或者 helm
命令的时候,如果用户没能完成认证,就会打开浏览器窗口进行登录,这样就无需使用 KUBECONFIG 变量了。
我们也会配置基于群组的访问控制,所以我们可以创建一个 KubernetesAdminstrators
组,从而让组中所有用户具备 cluster-admin
权限。
如果我们在 Keycloak 中移除用户(或者从特定组中移除用户),对应用户就会失去权限。
我们会使用 OpenID Connect。官网文档中介绍了这一特性的原理。
缺省情况下,要给 Kubernetes 加入 OIDC 认证配置,需要修改 API Server 的参数。只有一些托管 Kubernetes 产品(例如 AWS 和 GCP)提供了这种手段,用于连接它们各自的 IAM 系统。
我们将使用来自 JetStack 的 kube-oidc-proxy 来解决这个问题。这个工具提供一个代理服务器来管理 OIDC 认证,用户连接到这个代理服务器时,服务会给通过认证的用户提供所需的权限。这种方法是通用的,也就是说我们可以用同样的方法来管理所有的托管和非托管集群。
首先我们要在 Keycloak 中创建一个新客户端,其 ID 为 kube-oidc-proxy
,协议为 openid-connect
,并且设置该客户端的参数:
Access Type:confidential
,这需要生成一个应用 Secret。
Valid Redirect URLs:http://localhost:8000
和 http://localhost:18000
。kubelogin 会使用这些网址作为回调,在 kubectl
进行登录时,就会打开浏览器窗口进行 Keycloak 进行认证。
保存新应用,就会出现一个新的 Credentials
标签,需要在这个标签里生成客户端 Secret,这个 Secret 将会在后续步骤中和 Client ID 一起使用。
客户端创建之后,就要配置 Kube OIDC Proxy 了。在 kube-oidc-proxy/values-kube-oidc.yml
包含一个样本:
oidc:
clientId: kube-oidc-proxy
issuerUrl: https://sso.ssotest.staging.talkingquickly.co.uk/auth/realms/master
usernameClaim: sub
extraArgs:
v: 10
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
nginx.ingress.kubernetes.io/backend-protocol: HTTPS
hosts:
- host: kube.ssotest.staging.talkingquickly.co.uk
paths:
- /
tls:
- secretName: oidc-proxy-tls
hosts:
- kube.ssotest.staging.talkingquickly.co.uk
有几个需要进行定制的点:
issuerUrl
:Keycloak 的实例地址,其中包含了 realm
信息(这里我们使用的是缺省的 master realm)。
Ingress 定义的主机名。这个 URL 会用来替代 Kubernetes API 的 URL,我们的 SSO 登录设置完毕之后,Kubeconfig 文件会指向这个地址而非原有的 Kubernetes API。
extraArgs
中设置的 v: 10
要求 Kube OIDC Proxy 输出详细日志,便于排查问题。在生产环境中可以删除这一行。
接下来用 Helm 安装 Kube OIDC Proxy:
helm upgrade --install kube-oidc-proxy ./charts/kube-oidc-proxy --values kube-oidc-proxy/values-kube-oidc.yml
Kube OIDC Proxy 启动之后,就可以配置 kubectl
了。最简单的方式就是使用 kubelogin
。它是一个 kubectl
的插件,安装插件之后,如果执行 kubectl
,就会打开一个浏览器窗口,让用户在其中登录 Keycloak。登录之后它会负责刷新 Token,并负责会话过期之后的重新认证。
项目主页提供了该插件的安装方法,homebrew 用户可以用 brew install int128/kubelogin/kubelogin
轻松完成,否则的话,推荐用 krew 管理 kubectl
插件,这样就可以用 kubectl krew install oidc-login
进行安装了。
接下来就是创建一个 kubeconfig.yml
文件,内容(kubelogin/kuebconfig.yml
)如下:
apiVersion: v1
clusters:
- cluster:
server: https://kube.ssotest.staging.talkingquickly.co.uk
name: default
contexts:
- context:
cluster: default
namespace: identity
user: oidc
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: oidc
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- oidc-login
- get-token
# - -v1
- --oidc-issuer-url=https://sso.ssotest.staging.talkingquickly.co.uk/auth/realms/master
- --oidc-client-id=kube-oidc-proxy
- --oidc-client-secret=a32807bc-4b5d-40b7-8391-91bb2b80fd30
- --oidc-extra-scope=email
- --grant-type=authcode
command: kubectl
env: null
provideClusterInfo: false
需要修改的内容:
server
:Kube OIDC Proxy 的 Ingress URL;
oidc-issuer-url
:和 Kube OIDC Proxy 中配置的 Keycloak URL;
oidc-client-secret
:Keycloak 客户端应用的 Secret;
-v1
:可选项,用于输出更多日志信息。
接下来执行:
export KUBECONFIG=./kubelogin/kubeconfig.yml
kubectl get pods
本文不会涉及 kubeconfig 文件的管理方法,而如果你还没有这方面的管理经验,强烈推荐使用 direnv 和 kubectx 的组合。我的 Debian 远程开发环境和 OSX 配置里面都提供了开箱可用的这两个工具。
export KUBECONFIG=./kubelogin/kubeconfig.yml
这个用法仅在同一个终端会话中生效,所以如果切换到新的终端,或者关闭重新打开你的终端,这个配置就会退回到 Shell 缺省的 KUBECONFIG
环境变量。
当我们执行上面命令的时候,会打开一个浏览器,用户需要在浏览器里登录 Keycloak。
然而我们会看到错误信息:
Error from server (Forbidden): pods is forbidden: User "oidcuser:7d7c2183-3d96-496a-9516-dda7538854c9" cannot list resource "pods" in API group "" in the namespace "identity"
Kubernetes 知道当前用户是 oidcuser:7d7c2183-3d96-496a-9516-dda7538854c9
,说明我们的用户已经通过认证,但是这个用户当前却又无权进行任何操作。
我们可以创建一个 Cluster Role Binding,并把 cluster-admin
角色绑定上去。
我们需要另开一个终端,也就是我们还没有修改 KUBECONFIG
的终端会话,这样我们就会使用一个 cluster-admin
权限来操作集群了。
kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='oidcuser:OUR_USER_ID'
把 OURUSERID
替换为登录用户的 Keycloak ID(可以参看上面的消息)。
oidcuser:
前缀是 Kueb OIDC Proxy 配置中的 usernamePrefix:
和 oidcuser:
。这一措施能够防止不同的用户系统之间造成冲突。
前面的步骤让我们的 kubectl
用 Keycloak 用户登录集群。然而为每个用户创建一个 Cluster Role Binding 是个很麻烦的事情。
要解决这个问题就要靠群组,我们会对 OIDC 实现进行配置,使其感知到 Keycloak 的群组。我们可以在 Keycloak 创建一个 KubernetesAdmin
组,组中所有用户都使用同一个 Cluster Role Binding 被授予 cluster-admin
权限。
首先在 Keycloak 上创建一个 KubernetesAdmin
群组,然后在群组中创建一个新用户。
接下来要更新我们的 Keycloak 客户端,把用户所属群组的信息包含在 JWT 中。
要完成这个任务要回到 Keycloak 客户端 kube-oidc-client
条目中,并选择 Mapper
分页,点击 Create
。
输入如下内容:
Name:Groups
;
Mapper Type:Group Membership
;
Full Group Path:Off
;
然后保存。
如果在 kubelogin/kubeconfig.yml
文件中取消 # - -v1
中的注释符,并删除 ~/.kube/cache/oidc-login/
的内容然后执行 kubectl get pods
,会再次进行登录,我们会看到 JWT 信息中包含了我们的群组信息:
{
...
"groups": [
"DockerRegistry",
"Administrators",
"KubernetesAdmins"
],
...
}
我们可以创建一个 Cluster Role Binding,让每个 KubernetesAdmin
中的每个成员都有 cluster-admin
的访问能力:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: oidc-admin-group
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: oidcgroup:KubernetesAdmins
在我们的 Kube OIDC Proxy 配置中,使用 groupsPrefix: "oidcgroup:"
的配置为群组名称加上了 oidcgroup
前缀,防止和 Kubernetes 中的其他分组造成冲突。
把这个 YAML 提交给集群:
kubectl apply -f ./group-auth/cluster-role-binding.yml
然后就可以删除前面单独创建的 Cluster Role Binding:
kubectl delete clusterrolebinding oidc-cluster-admin
上述动作完成之后,就能用 kubectl get po
之类的简单命令来验证工作成果了。
上述授权只是用于测试的,实际工作中应该创建更加严格的授权,例如一些只能在特定命名空间中工作的用户。
本章节完成后,只需要给 Ingress 加入注解,就能用 Keycloak 为其他应用提供认证功能。这就给我们一种方便易用的替换 Basic Auth 的方法。
一部分运行在 Kubernetes 中的第三方应用都支持 OIDC 或者 LDAP 的登录。在部署自研应用时,可以使用 Keycloak 来管理登录,而无需额外开发 OIDC 或者 LDAP 的集成代码。
下面将使用 OAuth2 Proxy 给一个简单的 Nginx 容器加入认证功能。之后观察一下如何访问应用,如何解码 Keycloak JWT 并使用群组鉴权等特性。
可以用这种建议的方式对内部应用进行保护。在更复杂的环境中,还可以在 Keycloak 中创建一个 customers
realm,把认证和鉴权工作都交给 Keycloak。
Keycloak Gatekeeper/Louketo 在 2020 年 8 月进入 EOL,因此我们使用 OAuth2 Proxy 来完成这一任务。
首先要对 OAuth2 Proxy 进行配置,使之对接到 Keycloak,并使用 Helm 进行部署。
接下来会用 Helm 部署官方的 Nginx 容器镜像,用它作为测试应用,然后我们会使用 Keycloak 结合 Ingress 注解来对其进行访问控制。
最后我们还要看看示例应用如何获得登录用的信息,从而有能力进行更细粒度的访问控制。
Nginx 支持基于子请求结果的认证:当受保护页面接到请求时,Nginx 可以向一个额外的 URL 发送一个子请求,如果该 URL 返回了 2xx 的响应码,就允许接收这个请求,如果返回了 401 或 403,就会拒绝请求。
实际上我们无需深入理解上面的内容,因为 OAuth2 Proxy 连接到了 Keycloak 进行实际的认证,并给 Nginx 提供了适用的端点,来检查用户是否登录。
所以我们只要配置 OAuth2 Proxy 并给特定服务的 Ingress 加入合适的注解。
首先我们要在 Keycloak 创建一个客户端应用,创建一个新的 OpenID 连接应用,并作出如下设置:
Client ID:oauth2-proxy
Access Type:confidential
Valid Redirect URLs:https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/callback
,注意替换其中的域名。
保存新建项目,并打开新出现的 Credentials
页面,注意其中的 Secret
内容。
最后进入 Mappers
页面,选择 Create
:
Name:Groups
Mapper Type:Group Membership
Token Claim Name:groups
其它选项都设置为 On
保存数据。这一配置的含义是,属于本组的用户会返回给 OAuth2 Proxy 并随后返回给应用。
OAuth2 Proxy 有一个 Keycloak
Provider,但是这里我们会使用通用的 OIDC Provider。这是一个更通用的解决方案,并提供了自动刷新 Cookie 等 Keycloak Provider 所没有的一些功能。目前 OAuth2 Proxy 团队还在讨论修改 Keycloak Provider 为 OIDC Provider。
下一步可以创建 OAuth2 Proxy 配置,在 oauth2-proxy/values-oauth2-proxy.yml
中包含一个样本:
# Oauth client configuration specifics
config:
clientID: "oauth2-proxy"
clientSecret: "YOUR_SECRET"
# Create a new secret with the following command
# openssl rand -base64 32 | head -c 32 | base64
cookieSecret: "YOUR_COOKIE_SECRET"
configFile: |-
provider = "oidc"
provider_display_name = "Keycloak"
oidc_issuer_url = "YOUR_ISSUER"
email_domains = [ "*" ]
scope = "openid profile email"
cookie_domain = ".ssotest.staging.talkingquickly.co.uk"
whitelist_domains = ".ssotest.staging.talkingquickly.co.uk"
pass_authorization_header = true
pass_access_token = true
pass_user_headers = true
set_authorization_header = true
set_xauthrequest = true
cookie_refresh = "1m"
cookie_expire = "30m"
ingress:
enabled: true
path: /
hosts:
- oauth.ssotest.staging.talkingquickly.co.uk
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
tls:
- secretName: oauth-proxy-tls
hosts:
- oauth.ssotest.staging.talkingquickly.co.uk
这里有一些需要自行配置的关键字段:
clientSecret:Keycloak 凭据页面里保存的 Secret
;
cookieSecret:可以用 openssl rand -base64 32 | head -c 32 | base64
命令随机生成;
loginurl、redeemurl、validate_url:应该根据实际的 URL 和 Realm 进行配置;
cookiedomain、whitelistdomain:需要根据实际的域名进行配置,例如这里使用的 .ssotest.staging.talkingquickly.co.uk
;
ingress hosts:设置为部署 OAuth2 Proxy 的 Ingress 主机名。
cookie_domain
和 whitelist_domain
都很重要,缺省情况下,OAuth2 Proxy 只对自己的主机名生效。所以 Cookie 只能指定到该主机名,重定向也只能对这个子域名生效。
缺省情况下,OAuth2 Proxy 会请求一个 api
scope
,这在 Keycloak 是不存在的,会返回 403 Invalid Scopes
的错误,因此要设置 scope = "openid profile email"
。
set_authorization_header
确保 JWT 被回传给 Nginx Ingress,这样就能确保 Header 被回传给应用,应用可以借此获取登录用户的信息。
最后,OAuth 报文中经常会传递较多的 Header 信息,因此这里设置 nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
,避免出现 Cookie "oauth2proxy not present"
或者 upstream sent too big header while reading response header from upstream
这样的错误。
同样地,因为 Stable 仓库即将淘汰,OAuth2 Proxy
Chart 还没迁移到新的仓库,所以我们在样例中保留了最近的版本,可以用如下命令进行安装:
helm upgrade --install oauth2-proxy ./charts/oauth2-proxy --values oauth2-proxy/values-oauth2-proxy.yml
安装结束,就可以浏览 Ingress 域名,会看到出现了 Sign in with Keycloak
的选项。
如果我们不小心使用管理员而非在 OAuth2 Proxy 登录会看到 403 之类的错误。
如果我们成功地登录到了 Kyecloak,会被重定向到一个 404 页面,这是因为目前还没定义待认证页面。我们不应该直接访问这个 URL,正常情况下,认证流程应该在浏览受保护页面时被自动触发。所以前面的登录动作只是为了验证功能而已。
现在我们已经完成了 OAuth2 Proxy 的配置,接下来就可以安装一个示例应用,并在 Ingress 定义中加入注解,将应用置于认证保护之后。
在这个例子中会安装一个简单的 Nginx,它只会提供一个 Welcome to nginx
的静态页面,不同的是这个页面会要求登录认证。读者要注意不要把这个 Nginx 和 Nginx Ingress 混为一谈。
这里用了 Bitnami 的 Nginx Helm Chart:
helm repo add bitnami https://charts.bitnami.com/bitnami
用如下代码配置我们的应用:
serverBlock: |
log_format withauthheaders '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$http_x_auth_request_access_token"';
add_header x-auth-request-access-token "$http_x_auth_request_access_token";
# HTTP Server
server {
# Port to listen on, can also be set in IP:PORT format
listen 8080;
include "/opt/bitnami/nginx/conf/bitnami/*.conf";
location /status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
access_log /dev/stdout withauthheaders;
}
ingress:
enabled: true
hostname: nginx-demo-app2.ssotest.staging.talkingquickly.co.uk
tls: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
nginx.ingress.kubernetes.io/auth-url: "https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/start?rd=$scheme://$best_http_host$request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-request-email, x-auth-request-access-token"
acme.cert-manager.io/http01-edit-in-place: "true"
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
service:
type: ClusterIP
这里定义的 serverBlock
和实际的认证流程无关。下面两条认证措施,都是把 Nginx 作为演示应用的:
修改日志行为,在日志输出中加入 x-auth-request-access-token
Header,这样我们就可以查看日志中的 Token,进行分析和测试。
自动为入站请求和响应加入 x-auth-request-access-token
Header,便于在浏览器进行查看。
把 Token 输出到日志是非常危险的操作,决不应该用在生产环境之中。
跟认证有关的行如下所示:
ingress:
annotations:
nginx.ingress.kubernetes.io/auth-url: "https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/start?rd=$scheme://$best_http_host$request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-request-email, x-auth-request-access-token"
acme.cert-manager.io/http01-edit-in-place: "true"
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
这里用 acme.cert-manager.io/http01-edit-in-place: "true"
注解来调用 Cert Manager 并设置响应 Header。nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
则是增加缓存用于存储 OAuth 2 Proxy Header。
nginx.ingress.kubernetes.io/auth-url
是第一处核心注解,指定了检查当前用户认证的 URL。当请求进入时,Nginx 会发送请求到这个网址,注意它发送的只是 Header 以及请求相关的 Cookie,而不包括 Body。URL 所对应的 Service(本例中就是 OAuth2 Proxy)负责根据 Cookie 和 Header 来验证用户的登录状态。如果用户已经过认证,服务会返回 2xx 状态码,然后这个请求就会传递给应用。如果未登录,请求就会转发给 nginx.ingress.kubernetes.io/auth-signin
中的 URL,启动认证流程。要给所有子域提供认证,就需要把 OAuth2 Proxy 的 Cookie 域设置为上级域名。
因为设置了 set_authorization_header = true
,当请求认证通过后,OAuth2 Proxy 就会在返回给 Nginx 的 2xx 响应中加入 x-auth-request-access-token
Header,其中包含了认证 Token,本例中这个 Token 是包含用户和会话的 JWT 信息。
缺省情况下,被认证应用是不能访问这个 Token 的,要获取认证用户的信息,或者该用户所属的群组,要获取这些信息,需要设置 nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-request-email, x-auth-request-access-token"
,它会通知 Nginx Ingress,从返回的 2xx 响应中获取这几个 Header,并传递给后端应用。这样后端应用就能够获取 Header,并解码 JWT 来获取用户的相关信息。
在这个简单的例子里,会把这些信息输出到日志里(不安全),并把信息响应给用户。如果访问示例应用的 Ingress URL,在本例中就是 https://nginx-demo-app2.ssotest.staging.talkingquickly.co.uk
,就会要求登录,然后重定向到 Welcome to nginx!
页面。
可以用浏览器的网络页来观察请求内容,会看到响应中的 x-auth-request-access-token
。
如果我们把 Header 内容拷贝出来进行解码(例如 https://jwt.io)就会看到类似内容:
{
...
"scope": "openid email profile",
"email_verified": false,
"name": "Ben Dixon",
"groups": [
"/DockerRegistry",
"/KubernetesAdmins",
"/Administrators"
],
"preferred_username": "talkingquickly",
"given_name": "Ben",
"family_name": "Dixon",
"email": "[email protected]"
}
在更复杂的系统中,后端系统会用这里的信息进行判断,根据用户和群组信息来展示不同内容。
我们已经完成了两阶段认证。当请求认证通过之后,OAuth2 Proxy 会跟 Keycloak 进行通信,并取得 Access Token。只要 OAuth2 Proxy 的 Cookie 存在并且有效,相应的请求就不会需要重新使用 Keycloak 进行认证。
使用 JWT 时,很可能会遭遇一个问题就是过期(缺省情况下,Keycloak 的 Access Key 寿命只有一分钟)。这个问题会导致一个麻烦,用户通过了 OAuth2 Proxy 的验证,但是传递给 x-auth-request-access-token
的 JWT 已经过期了。如果我们在应用中对 JWT 进行验证,会抛出 Token 无效的异常。
下面的配置可以解决这个问题:
cookie_refresh = "1m"cookie_expire = "30m"
cookie_refresh
告诉 OAuth2 Proxy 如果 OAuth2 Proxy Cookie 超过一分钟还没刷新,则刷新 Access Token。这就和 Keycloak 的行为一致了,不会再次把过期 Token 加入请求数据之中。
使用 OIDC Provider 而非 Keycloak Provider 的原因是,目前 Keycloak Provider 还不支持 Token 刷新。
cookie_expire
设置 OAuth2 Proxy 的 Cookie 生命周期为 30 分钟,超时之后用户会被转向 KeyCloak 重新进行验证。这就和 KeyCloak 的会话过期保持了同步。
在 OAuth2 Proxy 配置中加入一行 allowed_groups = ["/DemoAdmin"]
,就能够仅允许属于 DemoAdin
群组的用户进行登录。这种情况下,组外用户在登录失败的时候会看到一个 500 的返回码,而不是一个说明实际原因的页面。用户登录时,如果没有在许可组中找到的记录,OAuth2 Proxy 就会返回 400,如果我们看看 Nginx 的日志,会看到 auth request unexpected status: 400 while sending to client
这样的信息。
所以这种方法虽然适用于简单的内部应用,但是在应用中处理群组鉴权会有更好的用户体验。
jwt-ruby-example/main.rb
是一个简单的 Ruby 程序,其中包含了处理 Token 的技术。下面的代码很直白:
require 'jwt'public_key_string = """
PUBLIC_KEY_GOES_HERE
"""public_key = OpenSSL::PKey::RSA.new(public_key_string)
token = "TOKEN_GOES_HERE"decoded_token = JWT.decode token, public_key, true, { algorithm: 'RS256' }
puts decoded_token
PUBLIC_KEY_GOES_HERE
需要用公钥来替换,可以在 Keycloak 的 Realm Settings
-> Keys
-> RS256
-> Public Key
中找到。
而 TOKEN_GOES_HERE
需要从我们的应用日志或者是 Headers 中获取并使用 ruby main.rb
(在运行 bundle install
之后)解码获得。
注意 Keycloak Token 的缺省过期时间是 1 分钟,所以拷贝黏贴动作最好快一点。
输出内容会被解压为 Ruby Map。所以在完整的 Web 应用中(例如 Rails 或者 Sinatra),可以需要根据用户所属群组或者用户登录 Email 进行相应的判断。
Gitea 是一个开源的轻量级 Git 服务。顺便说一句,Gitea(尤其是和 Drone CI 协同)是我最喜欢的开源软件。
Gitea 的轻量和易用特性,非常适用于 Git Push 发布以及持续集成。
这里我们要配置 OpenLDAP 来进行中心化的用户管理并提供单点登录。我们也会配置 OpenID Connect,但是可能会有一些问题。
本文我们主要会聚焦于配置 Gitea 使用 LDAP 进行认证的过程,因此我们这里只会包括 Web 界面的设置。
可能需要根据集群现状编辑 gitea/values-gitea.yml
中的 Ingress 主机名以及 HTTPS 的相关配置:
itea:
domain: gitea-keycloak.ssotest.staging.talkingquickly.co.uk
protocol: http
installLock: "false"
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
hosts:
- host: gitea-keycloak.ssotest.staging.talkingquickly.co.uk
paths: ['/']
tls:
- secretName: gitea-keycloak-https-secret
hosts:
- gitea-keycloak.ssotest.staging.talkingquickly.co.uk
编辑结束之后,用 Gitea 安装:
helm3 upgrade --install gitea-keycloak ./charts/gitea --values ./gitea/values-gitea.yml
安装结束之后,我们在 Gitea Pod 里用客户端创建一个初始用户 administrator
,注意修改其中的 YOUR_PASSWORD
和 YOUR_EMAIL
字段:
kubectl exec -it --namespace identity \
$(kubectl get pods -n identity --selector='app.kubernetes.io/instance=gitea' -o jsonpath='{.items[0].metadata.name}') \
-- gitea admin user create --username YOUR_EMAIL --password YOUR_PASSWORD --email YOUR_EMAIL --admin --access-token --must-change-password=false
这样我们就能登录 Gitea 实例了。
Gitea 支持的 ODIC 登录仅适用于现存用户,不利于中心化的用户管理,所以我们选用 LDAP。
Github 上的 Gitea 仓库有个 Issue 解释了 OIDC 和 LDAP 的差异。
用 administrator
身份登录 Gitea,并进入 Site Administrator
-> Authentication Sources
,在 Add Authentication Source
选择 LDAP (via BindDN)
作为认证源。进行如下配置:
Authentication Name:OpenLDAP
;
Security Protocol:Unencrypted
;
Host:openldap.identity.svc.cluster.local
;
Port:389
;
Bind DN:cn=readonly,dc=k4stest4,dc=talkingquickly,dc=co,dc=uk
;
Bind Password:这个应该是在 values-openldap.yml
文件中设置的只读用户密码;
User Search Base:ou=People,dc=k4stest4,dc=talkingquickly,dc=co,dc=uk
注意根据实际情况替换 dc
。
User Filter:(&(objectClass=inetOrgPerson)(uid=%s))
的配置允许所有用户登录 Gitea,当然可以创建更复杂的过滤器,只允许特定群组(例如 Engineers
)成员登录,How to write ldap search filters 一文讲解了过滤器编写方面的知识。uid=%s
让用户可以使用用户名登录,可以修改过滤器同时匹配邮件和用户名;
Username Attribute:uid
;
First Name Attribute:cn
;
Surname attribute:sn
;
Email Attribute:mail
。
Gitea LDAP 文档 中介绍了更多的细节内容。
注意如果我们要用配置管理工具管理 Gitea,可以使用前面创建管理账号的方法。要用 CLI 创建 LDAP 配置可以参考 Gitea 的命令文档。
如果回到 Gitea 的登录页面,我们会发现可以直接用 Keycloak 中创建的用户凭据登录。注意要使用 master realm
中的用户而非管理用户。
这个过程没有重定向,密码认证会在后台执行。如果我们进入该用户的 security settings
页面,因为用户是外部托管的,无法在 Gitea 中修改密码,只能在 Keycloak 做管理。
OpenID Connect 无法在 Gitea 中使用完整的 SSO。(Gitea 的)底层用户必须已经存在,对于已经登录的用户是可以配置的,要配置 Keycloak 的 OIDC,从而用户可以通过 Keycloak 流程进行登录,而不用直接在 Gitea 中输入他们的 Keycloak 用户名和密码。
这种方式的唯一好处就是,扩展使用 Keycloak 让用户能够更方便的登录,少输入用户名和密码。所以这个选项不像其它方法一样完整。
在 Keycloak 管理界面中,进入侧面菜单的 Clients
页面,并选择 Create
。给 Gitea 输入 Client ID
并设置 Client Protocol
为 openid-connect
,设置如下内容:
Name:Gitea
;
Access Type:confidential
要生成客户端 Secret,就需要这样填写;
Valid Redirect URI’s:https://GITEA_INGRESS_URL/
(例如 https://gitea-keycloak.k4stest4.talkingquickly.co.uk/*)
保存之后,就可以在 Credentials
页面中找到客户端的 Secret。
在 Gitea 中进入 Site Administration
并选择 Authentication Sources
,然后选择 Add Authentication Source
,填写下列内容:
Authentication Type:OAuth2
;
Authentication Name:Keycloak
;
OAuth2 Provider:OpenID Connect
;
Client ID:gitea
(创建客户端应用时输入的值);
Client Secret:YOUR_SECRET
(在 Keycloak 客户端页面的 Credentials
卡片上为 Gitea 客户端创建的 Secret);
OpenID Connect Auto Discovery URL:https://YOUR_KEYCLOAK_INGRESS_URL/auth/realms/master/.well-known/openid-configuration
用 Keycloak 的 Ingress 主机名替换 YOUR_KEYCLOAK_INGRESS_URL
。
需要着重关注的是,Gitea 会在创建 Provider 的时候进行证书认证,所以如果 SSL 证书无效的时候是无法完成的。
在尝试登录之前,我们需要给在 Keycloak 中创建的用户设置一个密码。可以通过进入 Keycloak 用户页面,选择 Credentials
卡片并设置一个密码,如果设置 Temporary
为 1,则用户登录时,会被要求设置新密码。
这样我们就可以进入 Gitea 的登录页面,点击 Sign in with OpenID Connect
选项(如果已经登录,就需要登出当前用户)。这样会重定向到 Keycloak 登录页面,在这页面中我们可以用前面创建的 Keycloak 用户名进行登录。
使用 Keycloak 的用户名和密码进行登录之后,就会重定向回到 Gitea。
本节我们会讲解如何使用 Keycloak 作为 Docker 镜像库的认证层。这样用户必须使用 Keycloak 中的有效凭据完成 docker login
才能够进行 push
和 pull
。注意这里没有什么访问控制,所有 Keycloak 用户都能够对任何镜像执行任何动作。要进行细粒度的控制,还是需要使用 Harbor。
Keycloak 的 Helm values 文件中需要加入如下内容:
args:
- -Dkeycloak.profile.feature.docker=enabled
这样就能启用 Docker 镜像库的支持了。
在 Keycloak 中创建新的客户应用,命名为 simple-docker-registry
,协议选择 docker-v2
。在后续界面中进入 install
卡片,选择 Docker Compose YAML
,然后点击 Download
。我们并不是要使用 Docker Compose,这个过程是为了便捷地获取证书,用于创建 Kubernetes Secret。
从 Keycloak 中得到 Docker Compose YAML 压缩包之后,解压到本地文件夹,会看到其中包含了 certs/localhost_trust_chain.pem
文件。我们可以用下面的命令创建 Kubernetes:
kubectl create secret generic docker-registry-auth-token-rootcertbundle --from-file YOUR_PATH_TO/certs/localhost_trust_chain.pem
上述命令中需要用本地路径来替换 YOUR_PATH_TO/certs/localhost_trust_chain.pem
。执行之后就创建了一个名为 docker-registry-auth-token-rootcertbundle
的 Secret,其中包含了必要的证书内容。
用 Helm 安装 Docker 镜像仓库的文档:https://github.com/twuni/docker-registry.helm
。我们的配置文件大致如下:
configData:
auth:
token:
realm: https://sso.ssotest.staging.talkingquickly.co.uk/auth/realms/master/protocol/docker-v2/auth
service: simple-docker-registry
issuer: https://sso.ssotest.staging.talkingquickly.co.uk/auth/realms/master
rootcertbundle: /root-cert-bundle/localhost_trust_chain.pem
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
hosts:
- registry-keycloak.ssotest.staging.talkingquickly.co.uk
tls:
- hosts:
- registry-keycloak.ssotest.staging.talkingquickly.co.uk
secretName: keycloak-registry-tls-secret
extraVolumes:
- name: docker-registry-auth-token-rootcertbundle
secret:
secretName: docker-registry-auth-token-rootcertbundle
extraVolumeMounts:
- mountPath: /root-cert-bundle
name: docker-registry-auth-token-rootcertbundle
readOnly: true
这里的配置来源于 Keycloak 文档,configData
字段配置使用基于 Keycloak 的 Token 认证。
接下来需要更新 Ingress 定义,加入 Docker 镜像库的 URL,例如 registry-keycloak.ssotest.staging.talkingquickly.co.uk
。nginx.ingress.kubernetes.io/proxy-body-size: "0"
这一行注解移除了 Ningx 的最大 Body 长度限制,避免推送大镜像时出现的 413 Request Entity Too Large
错误信息。
extraVolumes
节创建了一个存储卷,用来加载我们的 docker-registry-auth-token-rootcertbundle
Secret。extraVolumeMounts
一节将这个卷加载到 Docker 镜像库的 /root-cert-bundle
路径,和 configData
中配置的 rootcertbundle
一致。
使用 Helm 安装镜像仓库:
helm repo add twuni https://helm.twun.io
helm upgrade --install simple-docker-registry twuni/docker-registry --values ./docker-registry/values-docker-registry.yml
我们需要等待一段时间,让 LetsEncrypt 生成证书,我们可以用 kubectl get certificates
命令查看进度。完成之后就能够进行登录了:
docker login registry-keycloak.ssotest.staging.talkingquickly.co.uk
上边的 URL 应该替换为我们的 Ingress 地址。可以输入 Keycloak 用户名进行认证:
sername: someusername
Password:
WARNING! Your password will be stored unencrypted in /home/ben/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
测试一下镜像推送和拉取:
docker image tag SOME_IMAGE_REF registry-keycloak.ssotest.staging.talkingquickly.co.uk/SOME_NAME
docker push registry-keycloak.ssotest.staging.talkingquickly.co.uk/SOME_NAME
docker pull registry-keycloak.ssotest.staging.talkingquickly.co.uk/SOME_NAME
会看到两种操作都成功了。
登出再试试看:
docker logout registry-keycloak.ssotest.staging.talkingquickly.co.uk
docker pull registry-keycloak.ssotest.staging.talkingquickly.co.uk/SOME_NAME
会看到拒绝访问的错误信息。
为了访问仓库中的镜像,需要创建合适的 Image Pull Secret,可以参看 Kubernetes 文档完成这个过程。
我们现在就有了一个只允许 Keycloak 认证用户访问的 Docker 镜像库。想要更高级的配置,例如只有特定用户才能访问仓库,或者更细粒度的访问控制,应该换用 Harbor。
官方提供了 Helm Chart。
和多数 Helm Chart 一样,values 文件内容相当丰富。接下来我们会定制 Ingress 内容和 TLS 证书。必须在安装完成之后使用 Web UI 或者 API 配置 OIDC。我们的 Values 配置包含如下内容:
expose:
type: ingress
tls:
certSource: secret
secret:
secretName: harbor-ingress-tls
ingress:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
hosts:
core: core.harbor.ssotest.staging.talkingquickly.co.uk
harborAdminPassword: 85nsafg87ehfgk0fgsgfg6u
externalURL: https://core.harbor.ssotest.staging.talkingquickly.co.uk
secretKey: "8d10dlskeit8fhtg"
notary:
enabled: false
metrics:
enabled: true
有几个需要注意的配置:
certSource
:secret
,其中包含 secretName: harbor-ingress-tls
告知 Harbor 使用 Cert Manager 为 Ingress 生成的证书,而不是自行生成。这样就避免 docker login
时出现 x509: certificate signed by unknown authority
;
core
:Ingress URL 应该用 Harbor URL 替换;
harbourAdminPassword
、externalURL
以及 secretKey
:都要替换为当前环境的配置内容,secretKey
应该是一个随机的 16 字符。
加入 Helm 仓库,并安装 Harbor:
helm repo add harbor https://helm.goharbor.io
helm upgrade --install harbor-registry harbor/harbor --values=./harbor/values-harbor.yml
命令结束之后,我们就可以使用 Ingress URL 访问 Harbor 了,用户名是 admin
,密码则是 harbourAdminPassword
中的配置内容。这些组件启动需要一段时间,通常还会看到一些 CrashLoopBackoff
之类的信息出现。
记住还不能用 Harbor 管理员账号进行 docker login
,目前 Harbor 中还没有任何的常规用户。因为要切换到 OIDC 登录,所以除了管理用户,我们不会创建任何常规用户。如果我们现在创建了测试用户,然后再删掉,我们还是不能切换到 OIDC 登录。
在 Keycloak 中创建一个新的客户端应用,ID 为 harbor
,客户端协议为 openid-connect
,并进行如下配置:
Access Type:confidential
Valid Redirect URIs:https://YOUR_HARBOR_CORE_INGRESS_DOMAIN/c/oidc/callback
然后保存客户端应用配置,记录 credentials
卡片的内容。
最后来到客户端应用的 Mappers
卡片创建如下的协议映射关系:
Name:Groups
;
Mapper Type:Group Membership
;
Token Claim Name:groups
;
All Other Options:On
。
通过 Ingress 地址使用用户名 admin
和 harborAdminPassword
中配置的密码登录到 Harbor 界面。进入 Administration
页面,接下来打开 Configuration
并选择 Authentication
卡片,修改 Auth Mode
为 OIDC
,输入下列配置:
OIDC Provider Name:Keycloak
;
OIDC Endpoint:https://YOUR_KEYCLOAK_BASE_URL/auth/realms/YOURREALM
;
OIDC Client ID:harbor
;
OIDC Client Secret:前面记录的客户端应用凭据;
Group Claim Name:groups
;
OIDC Scope:openid,profile,email,offline_access
;
Verify Certificate:根据实际情况决定;
Automatic Onboarding:checked
;
Username Claim:preferred_username
。
然后就可以使用 Test OIDC Server
按钮来测试配置,成功后点击 Save
。
如果我们现在登出管理账号(或者启动一个匿名浏览器),回到 Harbor Core 的 Ingress URL,会看到一个 Login with OIDC Provider
选项。选择这个选项之后,会被重定向到 Keycloak 进行登录,这里我们应该使用一个常规 Keycloak 用户(Master Realm),不要用 Keycloak 管理用户。登录到 Harbor 之后,Harbor 会自动根据 Keycloak 的配置创建一个用户名。
回到管理员账号,进入Administrator
和 Groups
,就会看到 Keycloak 用户所属群组已经被复制到 Harbor。这意味着可以把特定的群组连接到特定项目,从而自动授予用户某个项目的访问权限。
默认情况下所有用户都能创建项目。所有 Keycloak 用户都能登录到 Harbor,所以最好只允许管理员能够创建项目,可以用 Harbor 的 Administration/Configuration/System Settings
来完成这一限制。
可以用管理员身份进行测试,创建一个叫做 test1
的私有项目,浏览项目的 Members
卡片,选择 + Group
,输出 /Administrators
作为群组名称,并设置角色为 Project Admin
。Keyclok 的 Administrators
所有用户都能自动被授予该项目的 Project Admin
权限。
假设我们创建了叫做 test1
的私有项目,并让我们的 Keycloak Master Realm 账号能够访问这个项目,我们就能够使用 Docker 客户端登录了,例如 docker login core.harbor.ssotest.staging.talkingquickly.co.uk
。
然后输入 Keycloak 的 Master realm 中的用户名。而密码不应该使用 Keycloak 的密码,而是从 Harbor 获取,在用户页面右上角选择 User Profile
,在其中拷贝 CLI 密码。
然后尝试推送镜像:
docker tag SOURCE_IMAGE[:TAG] core.harbor.ssotest.staging.talkingquickly.co.uk/test1/REPOSITORY[:TAG]
docker push core.harbor.ssotest.staging.talkingquickly.co.uk/test1/REPOSITORY[:TAG]
如果我们要在 CI 服务器或者 Kubernetes 中使用这个仓库,可以在 Harbor 中进入 Robot Accounts
卡片生成受限的访问 Token。
Ansible 之类的自动化环境中是非常需要从命令行中完成任务的。因此 Harbor 提供了完善的 API,用管理员账号登录,点击底部的 Harbor API v2.0
,就能通过 Swagger 查看 API 文档。缺省情况下可以用 YOUR_INGRESS_URL/api/v2.0/
访问 API。要查看当前配置可以使用:
curl -u "admin:HARBOR_ADMIN_PASSWORD" -H "Content-Type: application/json" -ki YOUR_INGRESS_URL/api/v2.0/configurations
目前为止,Harbor 官网文档有些滞后,实际的命令返回内容中,为现有配置的可选项目提供了更好的概括信息。用命令行设置 OIDC 认证:
curl -X PUT -u "admin:YOUR_ADMIN_PASSWORD" -H "Content-Type: application/json" -ki YOUR_HARBOR_CORE_INGRESS_URL/api/v2.0/configurations -d'{"auth_mode":"oidc_auth", "oidc_name":"Keycloak Auth", "oidc_endpoint":"YOUR_KEYCLOAK_REALM_INGRESS", "oidc_client_id":"harbor", "oidc_client_secret":"YOUR_KEYCLOAK_CLIENT_SECRET", "oidc_scope":"openid,profile,email,offline_access", "oidc_groups_claim":"groups", "oidc_auto_onboard":"true", "oidc_user_claim":"preferred_username"}'
返回状态码为 200,表明我们已经成功地完成了 Keycloak 认证设置。然后做出限制,只有管理员才能创建项目:
curl -X PUT -u "admin:YOUR_ADMIN_PASSWORD" -H "Content-Type: application/json" -ki YOUR_HARBOR_CORE_INGRESS_URL/api/v2.0/configurations -d '{"project_creation_restriction":"adminonly"}'
Harbor API 非常全面,例如我们可以使用 API 创建项目,并为群组授权访问该项目。因此它的 Swagger API 非常值得一看。
要在 Kubernetes 中访问这个仓库,需要根据官方文档,使用项目的 Robot Token
创建合适的 Image Pull Secret。
我们现在有了一个自托管的 Docker 镜像仓库,并且完全集成了 Keycloak 进行认证。如果想要用 Ansible、Chef 之类的配置管理工具,还能使用命令行完成这些配置。
Nginx Ingress 控制器:https://kubernetes.github.io/ingress-nginx/
OpenLDAP OUs:https://www.theurbanpenguin.com/openldap-ous/
LDAPSearch:https://www.splunk.com/en_us/blog/tips-and-tricks/ldapsearch-is-your-friend.html
CN 和 DC:https://stackoverflow.com/questions/18756688/what-are-cn-ou-dc-in-an-ldap-search
Keycloak Chart:https://github.com/codecentric/helm-charts/tree/master/charts/keycloak
Issue:https://github.com/osixia/docker-openldap/issues/304
使用搜索操作符:https://docs.oracle.com/cd/E19623-01/820-6169/searching-for-special-entries-and-attributes.html
ldapsearch Examples:https://docs.oracle.com/cd/E19450-01/820-6169/ldapsearch-examples.html
Kubernetes 和 OpenID:https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens
kube-oidc-proxy:https://github.com/jetstack/kube-oidc-proxy
kubelogin:https://github.com/int128/kubelogin
kubelogin
:https://github.com/int128/kubelogin
Kube-Login:https://github.com/int128/kubelogin
krew:https://krew.sigs.k8s.io/docs/user-guide/setup/install/
direnv:https://direnv.net/
kubectx:https://github.com/ahmetb/kubectx
Debian 远程开发环境:http://www.talkingquickly.co.uk/2021/01/debian-dev-environment-for-remote-vscode/
OSX 配置:http://www.talkingquickly.co.uk/2021/01/macos-setup-with-ansible/
OAuth2 Proxy:https://github.com/oauth2-proxy/oauth2-proxy
官方的 Nginx 容器镜像:https://hub.docker.com/_/nginx
基于子请求结果的认证:https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/
Nginx Ingress:https://kubernetes.github.io/ingress-nginx/
https://jwt.io:`https://jwt.io/`
How to write ldap search filters:https://confluence.atlassian.com/kb/how-to-write-ldap-search-filters-792496933.html
Gitea LDAP 文档:https://docs.gitea.io/en-us/authentication/
Gitea 的命令文档:https://docs.gitea.io/en-us/command-line/
https://github.com/twuni/docker-registry.helm
:https://github.com/twuni/docker-registry.helm
Keycloak 文档:https://www.keycloak.org/docs/4.8/securing_apps/#docker-registry-configuration
Kubernetes 文档:https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
Harbor 官方 Helm 文档:https://github.com/goharbor/harbor-helm
Harbor 的 values 文件:https://github.com/goharbor/harbor-helm/blob/master/values.yaml
Harbor 官网文档:https://goharbor.io/docs/1.10/install-config/configure-user-settings-cli/
Pull Secret 官方文档:https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/