我们先创建一个示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
app: my-nginx
template:
metadata:
labels:
app: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
app: my-nginx
spec:
ports:
- port: 80
protocol: TCP
name: http
selector:
app: my-nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-nginx
namespace: default
spec:
ingressClassName: nginx # 使用 nginx 的 IngressClass(关联的 ingress-nginx 控制器)
rules:
- host: demo1.gl.sh.cn # 将域名映射到 my-nginx 服务
http:
paths:
- path: /
pathType: Prefix
backend:
service: # 将所有请求发送到 my-nginx 服务的 80 端口
name: my-nginx
port:
number: 80
# 不过需要注意大部分Ingress控制器都不是直接转发到Service
# 而是只是通过Service来获取后端的Endpoints列表,直接转发到Pod,这样可以减少网络跳转,提高性能
我们可以在 Ingress 对象上配置一些基本的 Auth 认证,比如 Basic Auth,可以用 htpasswd 生成一个密码文件来验证身份验证。
htpasswd -c auth foo
New password:
Re-type new password:
Adding password for user foo
然后根据上面的 auth 文件创建一个 secret 对象:
kubectl create secret generic basic-auth --from-file=auth
secret/basic-auth created
kubectl get secret basic-auth -o yaml
apiVersion: v1
data:
auth: Zm9vOiRhcHIxJFUxYlFZTFVoJHdIZUZQQ1dyZTlGRFZONTQ0dXVQdC4K
kind: Secret
metadata:
name: basic-auth
namespace: default
type: Opaque
然后对上面的 my-nginx 应用创建一个具有 Basic Auth 的 Ingress 对象:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-auth
namespace: default
annotations:
nginx.ingress.kubernetes.io/auth-type: basic # 认证类型
nginx.ingress.kubernetes.io/auth-secret: basic-auth # 包含 user/password 定义的 secret 对象名
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo' # 要显示的带有适当上下文的消息,说明需要身份验证的原因
spec:
ingressClassName: nginx # 使用 nginx 的 IngressClass(关联的 ingress-nginx 控制器)
rules:
- host: demo1-auth.gl.sh.cn # 将域名映射到 my-nginx 服务
http:
paths:
- path: /
pathType: Prefix
backend:
service: # 将所有请求发送到 my-nginx 服务的 80 端口
name: my-nginx
port:
number: 80
直接创建上面的资源对象,然后通过下面的命令或者在浏览器中直接打开配置的域名:
kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-with-auth nginx demo1-auth.gl.sh.cn 172.135.252.222 80 6m55s
# 测试
curl -v http://172.135.252.222 -H 'Host: demo1-auth.gl.sh.cn'
* Trying 172.135.252.222...
* TCP_NODELAY set
* Connected to 172.135.252.222 (172.135.252.222) port 80 (#0)
> GET / HTTP/1.1
> Host: demo1-auth.gl.sh.cn
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Thu, 16 Dec 2021 10:49:03 GMT
< Content-Type: text/html
< Content-Length: 172
< Connection: keep-alive
< WWW-Authenticate: Basic realm="Authentication Required - foo"
<
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host 172.135.252.222 left intact
* Closing connection 0
我们可以看到出现了 401 认证失败错误,然后带上我们配置的用户名和密码进行认证:
curl -v http://172.135.252.222 -H 'Host: demo1-auth.gl.sh.cn' -u 'foo:foo'
* Trying 172.135.252.222...
* TCP_NODELAY set
* Connected to 172.135.252.222 (172.135.252.222) port 80 (#0)
* Server auth using Basic with user 'foo'
> GET / HTTP/1.1
> Host: demo1-auth.gl.sh.cn
> Authorization: Basic Zm9vOmZvbw==
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 16 Dec 2021 10:49:38 GMT
< Content-Type: text/html
< Content-Length: 615
< Connection: keep-alive
< Last-Modified: Tue, 02 Nov 2021 14:49:22 GMT
< ETag: "61814ff2-267"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host 172.135.252.222 left intact
* Closing connection 0
可以看到已经认证成功了。除了可以使用我们自己在本地集群创建的 Auth 信息之外,还可以使用外部的 Basic Auth 认证信息,比如我们使用 https://httpbin.org 的外部 Basic Auth 认证,创建如下所示的 Ingress 资源对象:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# 配置外部认证服务地址
nginx.ingress.kubernetes.io/auth-url: https://httpbin.org/basic-auth/user/passwd
name: external-auth
namespace: default
spec:
ingressClassName: nginx
rules:
- host: external-demo1-auth.gl.sh.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-nginx
port:
number: 80
上面的资源对象创建完成后,再进行简单的测试:
kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
external-auth <none> external-demo1-auth.gl.sh.cn 80 72s
#测试
curl -k http://172.135.252.222 -v -H 'Host: external-demo1-auth.gl.sh.cn'
* Trying 172.135.252.222...
* TCP_NODELAY set
* Connected to 172.135.252.222 (172.135.252.222) port 80 (#0)
> GET / HTTP/1.1
> Host: external-demo1-auth.gl.sh.cn
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Thu, 16 Dec 2021 10:57:25 GMT
< Content-Type: text/html
< Content-Length: 172
< Connection: keep-alive
< WWW-Authenticate: Basic realm="Fake Realm"
<
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host 172.135.252.222 left intact
* Closing connection 0
然后使用正确的用户名和密码测试:
curl -k http://172.135.252.222 -v -H 'Host: external-demo1-auth.gl.sh.cn' -u 'user:passwd'
* Trying 172.135.252.222...
* TCP_NODELAY set
* Connected to 172.135.252.222 (172.135.252.222) port 80 (#0)
* Server auth using Basic with user 'user'
> GET / HTTP/1.1
> Host: external-demo1-auth.gl.sh.cn
> Authorization: Basic dXNlcjpwYXNzd2Q=
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 16 Dec 2021 10:58:31 GMT
< Content-Type: text/html
< Content-Length: 615
< Connection: keep-alive
< Last-Modified: Tue, 02 Nov 2021 14:49:22 GMT
< ETag: "61814ff2-267"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host 172.135.252.222 left intact
* Closing connection 0
如果用户名或者密码错误则同样会出现401的状态码:
curl -k http://172.135.252.222 -v -H 'Host: external-demo1-auth.gl.sh.cn' -u 'user:passwd123'
* Trying 172.135.252.222...
* TCP_NODELAY set
* Connected to 172.135.252.222 (172.135.252.222) port 80 (#0)
* Server auth using Basic with user 'user'
> GET / HTTP/1.1
> Host: external-demo1-auth.gl.sh.cn
> Authorization: Basic dXNlcjpwYXNzd2QxMjM=
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Thu, 16 Dec 2021 10:59:18 GMT
< Content-Type: text/html
< Content-Length: 172
< Connection: keep-alive
* Authentication problem. Ignoring this.
< WWW-Authenticate: Basic realm="Fake Realm"
<
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host 172.135.252.222 left intact
* Closing connection 0
当然除了 Basic Auth 这一种简单的认证方式之外,ingress-nginx 还支持一些其他高级的认证,比如我们可以使用 Gitlab OAuth 来认证 Kubernetes 的 Dashboard。
简单介绍一下关于oauth2 proxy介绍
oauth2 proxy是一个反向代理和静态文件服务器,使用提供程序(Google,GitHub和其他提供商)提供身份验证,以通过电子邮件,域或组验证帐户。
项目地址:https://github.com/oauth2-proxy/oauth2-proxy
认证过程的流程如下
在Gitlab配置OpenID应用
登录到Gitlab—>管理中心—>应用,创建一个应用
参数:
回调URL:指GitLab在用户通过身份验证后应将其发送到的端点,对于oauth2-proxy应该是https://<应用域名>/oauth2/callback
范围:应用程序对GitLab用户配置文件的访问级别。对于大多数应用程序,选择openid,profile和email即可。
创建完应用后,会生成一对ID和密钥,这个在后面会用到。
4.2 生成Cookie密钥
生成Cookie密钥。该Cookie密钥作为种子字符串以产生安全的cookie。
参考官方说明,使用base64编码,可利用以下的python脚本生成字符串。
import secrets
import base64
print(base64.b64encode(base64.b64encode(secrets.token_bytes(16))))
部署oauth2-proxy
在k8s中部署oauth-proxy,资源清单oauth2-gitlab.yaml和相关参数说明如下。更多的配置参数,可以参考官方文档
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: oauth2-proxy
name: oauth2-proxy
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
k8s-app: oauth2-proxy
template:
metadata:
labels:
k8s-app: oauth2-proxy
spec:
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 4180
protocol: TCP
args:
# OAuth提供者
- --provider=gitlab
# 上游端点的http网址
- --upstream=file:///dev/null
# 对具有指定域的电子邮件进行身份验证,可以多次给出,使用*验证任何电子邮件
- --email-domain=*
# 监听的地址
- --http-address=0.0.0.0:4180
# 设置安全(仅HTTPS)cookie标志
- --cookie-secure=false
# OAuth重定向URL
- --redirect-url=https://nginx-test.gl.sh.cn/oauth2/callback
# 跳过登录页面直接进入下一步
- --skip-provider-button=false
# 设置X-Auth-Request-User,X-Auth-Request-Email和X-Auth-Request-Preferred-Username响应头(在Nginx auth_request模式下有用)。与结合使用时--pass-access-token,会将X-Auth-Request-Access-Token添加到响应标头中
- --set-xauthrequest=true
# 跳过OPTIONS请求的身份验证
- --skip-auth-preflight=false
# 绕过OIDC端点发现
- --skip-oidc-discovery
# OpenID Connect发行者url,这里是gitlab的url
- --oidc-issuer-url=https://gitlab.gl.sh.cn
# 认证url
- --login-url=https://gitlab.gl.sh.cn/oauth/authorize
# token url
- --redeem-url=https://gitlab.gl.sh.cn/oauth/token
# 用于令牌验证的url
- --oidc-jwks-url=https://gitlab.gl.sh.cn/oauth/discovery/keys
env:
- name: OAUTH2_PROXY_CLIENT_ID
value: '85945b7195ab109377183837b9221bd299bc64b31fe272304a1c777e8e241d83'
- name: OAUTH2_PROXY_CLIENT_SECRET
value: '2f9782928b493686f387d18db9138e92607448cef045c81319967cc3e5ce4ba1'
# 安全cookie的种子字符串,可通过python脚本生成
- name: OAUTH2_PROXY_COOKIE_SECRET
value: 'VGlYNVBVOGw4UFgyRURzbERxVTRiZz09'
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: oauth2-proxy
name: oauth2-proxy
namespace: kube-system
spec:
type: NodePort
ports:
- name: http
port: 4180
protocol: TCP
targetPort: 4180
nodePort: 30020
selector:
k8s-app: oauth2-proxy
应用上面的资源清单,创建deployment和service
kubectl apply -f oauth2-gitlab.yaml
kubectl -n kube-system get pods -l k8s-app=oauth2-proxy
NAME READY STATUS RESTARTS AGE
oauth2-proxy-884695869-bkwns 1/1 Running 0 113s
上面通过nodeport单独暴露了oauth2-proxy应用,可以访问检查以确保浏览器可以正常打开
创建测试应用并配置Ingress
资源清单文件nginx.yaml如下,其中为该nginx应用配置了https证书
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.15
imagePullPolicy: IfNotPresent
name: nginx
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: kube-system
spec:
selector:
app: nginx
ports:
- name: nginx
port: 80
targetPort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: kube-system
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /
# 指定外部认证url
nginx.ingress.kubernetes.io/auth-url: "https://$host/oauth2/auth"
# 指定外部认证重定向的地址
nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/secure-backends: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- nginx-test.gl.sh.cn
secretName: nginx-test
rules:
- host: nginx-test.gl.sh.cn
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
# 将nginx应用的访问请求跳转到oauth2-proxy组件url
nginx.ingress.kubernetes.io/rewrite-target: "/oauth2"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/secure-backends: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
name: nginx-oauth2
namespace: kube-system
spec:
tls:
- hosts:
- nginx-test.gl.sh.cn
secretName: nginx-test
rules:
- host: nginx-test.gl.sh.cn
http:
paths:
- path: /oauth2
backend:
serviceName: oauth2-proxy
servicePort: 4180
应用上面的资源清单,创建相应资源
kubectl apply -f other/nginx.yaml
deployment.extensions/nginx unchanged
service/nginx unchanged
ingress.extensions/nginx unchanged
ingress.extensions/nginx-oauth2 unchanged
kubectl -n kube-system get po,svc,ing |grep nginx
pod/nginx-5ddcc6cb74-rnjlx 1/1 Running 0 3m
service/nginx ClusterIP 172.135.252.222 <none> 80/TCP 3m
ingress.extensions/nginx nginx-test.gl.sh.cn 80, 443 3m
ingress.extensions/nginx-oauth2 nginx-test.gl.sh.cn 80, 443 3m
测试外部认证
通过访问上面部署的nginx应用,在浏览器中进行测试,会被重定向到Gitlab登录页面;
输入账号,正确登录后,会被重定向回nginx应用。
流程分析
在请求登录外部认证的过程中查看oauth2-proxy的日志如下
172.16.1.110:49976 - - [2021/01/23 17:28:23] nginx-test.gl.sh.cn GET - "/oauth2/auth" HTTP/1.1 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15" 401 21 0.000
172.16.1.110:9991 - - [2021/01/23 17:28:23] nginx-test.gl.sh.cn GET - "/oauth2/start?rd=%2F" HTTP/1.1 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15" 302 341 0.000
172.16.1.110:9991 - admin@example.com [2021/01/23 17:28:32] [AuthSuccess] Authenticated via OAuth2: Session{email:admin@example.com user:root PreferredUsername: token:true id_token:true created:2021-01-23 17:28:32.440915913 +0000 UTC m=+2248.944621207 expires:2021-01-23 17:30:32 +0000 UTC refresh_token:true}
172.16.1.110:9991 - - [2021/01/23 17:28:32] nginx-test.gl.sh.cn GET - "/oauth2/callback?code=9b7f5425d48a41f213065a4ff5b3d20d76e6d241ba753c5cd63d1a405f48818e&state=5d868b96b1f539a28da2291404152b7c%3A%2F" HTTP/1.1 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15" 302 24 0.381
172.16.1.110:5610 - admin@example.com [2021/01/23 17:28:32] nginx-test.gl.sh.cn GET - "/oauth2/auth" HTTP/1.1 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15" 202 0 0.000
访问nginx应用的时候,Ingress nginx controller会向定义的auth-url发起认证,该认证由Ingress nginx controller发起,所以Ingress nginx controller对应的pod必须能够访问auth-url。
如果认证没有通过,Ingress nginx controller将客户端重定向到auth-signin。auth-signin是目标应用的 oauth2登录页面即oauth2-proxy。
客户端被重定向到oauth2登录页面后,自动进入Gitlab的登录页面,
用户登录Gitlab后,Gitlab再将客户端重定向到在Gitlab中配置的应用回调地址。
客户端访问回调地址后,oauth2_proxy在客户端设置cookie,并将客户端重定向到最初的访问地址。
带有cookie的客户端再次访问目标应用时,通过了auth-url的认证,成功访问到目标服务即nginx应用。
5、总结
本文以基于k8s部署的nginx服务为例,记录如何通过ingress和oauth2 proxy对接gitlab实现对应用没有代码侵入的外部认证。
最后,还要提到的一点是,我这里一开始使用的Gitlab是已有的10.8.4版本,调试了关于Oauth2-proxy的很多参数一直不成功,也没有找到解决办法,但是按照官方的配置与github对接时却没有报任何异常。最终通过提交issue得到了可能原因,即Gitlab的API版本可能不兼容,oauth2-proxy的开发测试成功版本的Gitlab在12.x版本以上。