“持续创造,加快成长!这是我参加「日新计划 6 月更文应战」的第13天,点击检查活动概况”。
大家好,我是乔克。
在Kubernetes中,APIServer是整个集群的中枢神经,它不仅连接了各个模块,更是为整个集群供给了拜访操控能力。
Kubernetes API的每个恳求都要经过多阶段的拜访操控才会被承受,包括认证、授权、准入,如下所示。 客户端(一般账户、ServiceAccount等)想要拜访Kubernetes中的资源,需求经过经过APIServer的三大过程才能正常拜访,三大过程如下:
- Authentication 认证阶段:判别恳求用户是否为能够拜访集群的合法用户。假如用户是个非法用户,那 apiserver会回来一个 401 的状况码,并停止该恳求;
- 假如用户合法的话,咱们的 apiserver 会进入到拜访操控的第二阶段 Authorization:授权阶段。在该阶段中apiserver 会判别用户是否有权限进行恳求中的操作。假如无权进行操作,apiserver 会回来 403的状况码,并相同停止该恳求;
- 假如用户有权进行该操作的话,拜访操控会进入到第三个阶段:AdmissionControl。在该阶段中 apiserver 的admission controller 会判别恳求是否是一个安全合规的恳求。假如终究验证经过的话,拜访操控流程才会结束。
这篇文章首要和大家评论认证环节。
认证
Kubernetes中支撑多种认证机制,也支撑多种认证插件,在认证过程中,只需一个经过则表明认证经过。
常用的认证插件有:
- X509证书
- 静态Token
- ServiceAccount
- OpenID
- Webhook
- …..
这儿不会把每种认证插件都介绍一下,首要讲讲Webhook的运用场景。
在企业中,大部分都会有自己的账户中心,用于办理职工的账户以及权限,而在K8s集群中,也需求进行账户办理,假如能直接运用现有的账户体系是不是会方便许多?
K8s的Webhook就能够完成这种需求,Webhook是一个HTTP回调,经过一个条件触发HTTP POST恳求发送到Webhook 服务端,服务端依据恳求数据进行处理。
下面就带大家从0到1开发一个认证服务。
开发Webhook
简介
WebHook的功用首要是接纳APIServer的认证恳求,然后调用不同的认证服务进行认证,如下所示。
这儿只是做一个Webhook的比如,目前首要完成了Github
和LDAP
认证,当然,认证部分的功用比较单一,没有考虑杂乱的场景。
Webhook开发
开发环境
软件 | 版别 |
---|---|
Go | 1.17.3 |
Kubernetes | v1.22.3 |
System | CentOS7.6 |
构建契合规范的Webhook
在开发Webhook的时分,需求契合Kubernetes的规范,详细如下:
- URL:auth.example.com/auth
- Method:POST
- Input参数
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"spec": {
"token": "<持有者令牌>"
}
}
- Output参数
假如成功会回来:
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
"username": "janedoe@example.com",
"uid": "42",
"groups": [
"developers",
"qa"
],
"extra": {
"extrafield1": [
"extravalue1",
"extravalue2"
]
}
}
}
}
假如不成功,会回来:
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": false
}
}
长途服务应该会填充恳求的 status 字段,以标明登录操作是否成功。
开发认证服务
(1)创立项目并初始化go mod
# mkdir kubernetes-auth-webhook
# cd kubernetes-auth-webhook
# go mod init
(2)在项目根目录下创立webhook.go,写入如下内容
package main
import (
"encoding/json"
"github.com/golang/glog"
authentication "k8s.io/api/authentication/v1beta1"
"k8s.io/klog/v2"
"net/http"
"strings"
)
type WebHookServer struct {
server *http.Server
}
func (ctx *WebHookServer) serve(w http.ResponseWriter, r *http.Request) {
// 从APIServer中取出body
// 将body进行拆分, 取出type
// 依据type, 取出不同的认证数据
var req authentication.TokenReview
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&req)
if err != nil {
klog.Error(err, "decoder request body error.")
req.Status = authentication.TokenReviewStatus{Authenticated: false}
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(req)
return
}
// 判别token是否包括':'
// 假如不包括,则回来认证失利
if !(strings.Contains(req.Spec.Token, ":")) {
klog.Error(err, "token invalied.")
req.Status = authentication.TokenReviewStatus{Authenticated: false}
//req.Status = map[string]interface{}{"authenticated": false}
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(req)
return
}
// split token, 获取type
tokenSlice := strings.SplitN(req.Spec.Token, ":", -1)
glog.Infof("tokenSlice: ", tokenSlice)
hookType := tokenSlice[0]
switch hookType {
case "github":
githubToken := tokenSlice[1]
err := authByGithub(githubToken)
if err != nil {
klog.Error(err, "auth by github error")
req.Status = authentication.TokenReviewStatus{Authenticated: false}
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(req)
return
}
klog.Info("auth by github success")
req.Status = authentication.TokenReviewStatus{Authenticated: true}
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(req)
return
case "ldap":
username := tokenSlice[1]
password := tokenSlice[2]
err := authByLdap(username, password)
if err != nil {
klog.Error(err, "auth by ldap error")
req.Status = authentication.TokenReviewStatus{Authenticated: false}
//req.Status = map[string]interface{}{"authenticated": false}
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(req)
return
}
klog.Info("auth by ldap success")
req.Status = authentication.TokenReviewStatus{Authenticated: true}
//req.Status = map[string]interface{}{"authenticated": true}
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(req)
return
}
}
首要是解析认证的恳求Token,然后将Token进行拆分判别是需求什么认证,Token的样例如下:
- Github认证:github:
- LDAP认证:ldap::
这样就能够获取到用户想用哪种认证,再掉详细的认证服务进行处理。
(3)创立github.go,供给github认证办法
package main
import (
"context"
"github.com/golang/glog"
"github.com/google/go-github/github"
"golang.org/x/oauth2"
)
func authByGithub(token string) (err error) {
glog.V(2).Info("start auth by github......")
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
tokenClient := oauth2.NewClient(context.Background(), tokenSource)
githubClient := github.NewClient(tokenClient)
_, _, err = githubClient.Users.Get(context.Background(), "")
if err != nil {
return err
}
return nil
}
能够看到,这儿只是做了一个简单的Token认证,认证的成果比较粗犷,假如err=nil
,则表明认证成功。
(4)创立ldap.go,供给ldap认证
package main
import (
"crypto/tls"
"errors"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/golang/glog"
"k8s.io/klog/v2"
"strings"
)
var (
ldapUrl = "ldap://" + "192.168.100.179:389"
)
func authByLdap(username, password string) error {
groups, err := getLdapGroups(username, password)
if err != nil {
return err
}
if len(groups) > 0 {
return nil
}
return fmt.Errorf("No matching group or user attribute. Authentication rejected, Username: %s", username)
}
// 获取user的groups
func getLdapGroups(username, password string) ([]string, error) {
glog.Info("username:password", username, ":", password)
var groups []string
config := &tls.Config{InsecureSkipVerify: true}
ldapConn, err := ldap.DialURL(ldapUrl, ldap.DialWithTLSConfig(config))
if err != nil {
glog.V(4).Info("dial ldap failed, err: ", err)
return groups, err
}
defer ldapConn.Close()
binduser := fmt.Sprintf("CN=%s,ou=People,dc=demo,dc=com", username)
err = ldapConn.Bind(binduser, password)
if err != nil {
klog.V(4).ErrorS(err, "bind user to ldap error")
return groups, err
}
// 查询用户成员
searchString := fmt.Sprintf("(&(objectClass=person)(cn=%s))", username)
memberSearchAttribute := "memberOf"
searchRequest := ldap.NewSearchRequest(
"dc=demo,dc=com",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
searchString,
[]string{memberSearchAttribute},
nil,
)
searchResult, err := ldapConn.Search(searchRequest)
if err != nil {
klog.V(4).ErrorS(err, "search user properties error")
return groups, err
}
// 假如没有查到成果,回来失利
if len(searchResult.Entries[0].Attributes) < 1 {
return groups, errors.New("no user in ldap")
}
entry := searchResult.Entries[0]
for _, e := range entry.Attributes {
for _, attr := range e.Values {
groupList := strings.Split(attr, ",")
for _, g := range groupList {
if strings.HasPrefix(g, "cn=") {
group := strings.Split(g, "=")
groups = append(groups, group[1])
}
}
}
}
return groups, nil
}
这儿的用户名是固定了的,所以不适合其他场景。
(5)创立main.go进口函数
package main
import (
"context"
"flag"
"fmt"
"github.com/golang/glog"
"net/http"
"os"
"os/signal"
"syscall"
)
var port string
func main() {
flag.StringVar(&port, "port", "9999", "http server port")
flag.Parse()
// 启动httpserver
wbsrv := WebHookServer{server: &http.Server{
Addr: fmt.Sprintf(":%v", port),
}}
mux := http.NewServeMux()
mux.HandleFunc("/auth", wbsrv.serve)
wbsrv.server.Handler = mux
// 启动协程来处理
go func() {
if err := wbsrv.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
glog.Errorf("Failed to listen and serve webhook server: %v", err)
}
}()
glog.Info("Server started")
// 优雅退出
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
<-signalChan
glog.Infof("Got OS shutdown signal, shutting down webhook server gracefully...")
_ = wbsrv.server.Shutdown(context.Background())
}
到此整个认证服务就开发完毕了,是不是很简单?
Webhook测验
APIServer增加认证服务
运用Webhook进行认证,需求在kube-apiserver里敞开,参数如下:
- –authentication-token-webhook-config-file 指向一个装备文件,其间描述 怎么拜访长途的 Webhook 服务
- –authentication-token-webhook-config-file 指向一个装备文件,其间描述 怎么拜访长途的 Webhook 服务
装备文件运用 kubeconfig 文件的格局。文件中,clusters 指代长途服务,users 指代长途 API 服务 Webhook。装备如下:
(1)、将装备文件放到相应的目录
# mkdir /etc/kubernetes/webhook
# cat >> webhook-config.json <EOF
{
"kind": "Config",
"apiVersion": "v1",
"preferences": {},
"clusters": [
{
"name": "github-authn",
"cluster": {
"server": "http://10.0.4.9:9999/auth"
}
}
],
"users": [
{
"name": "authn-apiserver",
"user": {
"token": "secret"
}
}
],
"contexts": [
{
"name": "webhook",
"context": {
"cluster": "github-authn",
"user": "authn-apiserver"
}
}
],
"current-context": "webhook"
}
EOF
(2)在kube-apiserver中增加装备参数
# mkdir /etc/kubernetes/backup
# cp /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/backup/kube-apiserver.yaml
# cd /etc/kubernetes/manifests/
# cat kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.0.4.9:6443
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- ......
- --authentication-token-webhook-config-file=/etc/config/webhook-config.json
image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.22.0
imagePullPolicy: IfNotPresent
......
volumeMounts:
......
- name: webhook-config
mountPath: /etc/config
readOnly: true
hostNetwork: true
priorityClassName: system-node-critical
securityContext:
seccompProfile:
type: RuntimeDefault
volumes:
......
- hostPath:
path: /etc/kubernetes/webhook
type: DirectoryOrCreate
name: webhook-config
status: {}
ps: 为了节省篇幅,上面省略了部分装备。
当修改完往后,kube-apiserver会自动重启。
测验Github认证
(1)在github上获取Token,操作如图所示
(2)装备kubeconfig,增加user
# cat ~/.kube/config
apiVersion: v1
......
users:
- name: joker
user:
token: github:ghp_jevHquU4g43m46nczWS0ojxxxxxxxxx
(3)用Joker用户进行拜访
回来成果如下,至于报错是因为用户的权限缺乏。
# kubectl get po --user=joker
Error from server (Forbidden): pods is forbidden: User "" cannot list resource "pods" in API group "" in the namespace "default"
能够在webhook上看到日志信息,如下:
# ./kubernetes-auth-webhook
I1207 15:37:29.531502 21959 webhook.go:55] auth by github success
从日志和成果能够看到,运用Github认证是OK的。
测验LDAP认证
LDAP简介
LDAP是协议,不是软件。
LDAP
是轻量目录拜访协议,英文全称是Lightweight Directory Access Protocol
,一般都简称为LDAP。按照咱们对文件目录的了解,ldap能够当作一个文件体系,类似目录和文件树。
OpenLDAP是常用的服务之一,也是咱们本次测验的认证服务。
装置OpenLDAP
OpenLDAP
的装置方式有许多,能够运用容器布置,也能够直接装置在裸机上,这儿采用后者。
# yum install -y openldap openldap-clients openldap-servers
# systemctl start slapd
# systemctl enable slapd
默认装备文件,位于/etc/openldap/slapd.d
, 文件格局为LDAP Input Format (LDIF)
, ldap目录特定的格局。这儿不对装备文件做太多的介绍,有爱好能够自己去学习学习【1】。
在LDAP上装备用户
(1)导入模板
ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/cosine.ldif
ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/nis.ldif
ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/inetorgperson.ldif
(2)创立base安排
# cat base.ldif
dn: dc=demo,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: ldap测验安排
dc: demo
dn: cn=Manager,dc=demo,dc=com
objectClass: organizationalRole
cn: Manager
description: 安排办理人
dn: ou=People,dc=demo,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Group,dc=demo,dc=com
objectClass: organizationalUnit
ou: Group
运用ldapadd
增加base。
ldapadd -x -D cn=admin,dc=demo,dc=com -w admin -f base.ldif
(3)增加成员
# cat adduser.ldif
dn: cn=jack,ou=People,dc=demo,dc=com
changetype: add
objectClass: inetOrgPerson
cn: jack
departmentNumber: 1
title: 大牛
userPassword: 123456
sn: Bai
mail: jack@demo.com
displayName: 中文名
运用ldapadd履行增加。
ldapadd -x -D cn=admin,dc=demo,dc=com -w admin -f adduser.ldif
(4)将用户增加到组
# cat add_member_group.ldif
dn: cn=g-admin,ou=Group,dc=demo,dc=com
changetype: modify
add: member
member: cn=jack,ou=People,dc=demo,dc=com
运用ldapadd履行增加。
ldapadd -x -D cn=admin,dc=demo,dc=com -w admin -f add_member_group.ldif
装备kubeconfig,进行ldap认证测验
(1)修改~/.kube/config
装备文件
# cat ~/.kube/config
apiVersion: v1
......
users:
- name: joker
user:
token: github:ghp_jevHquU4g43m46nczWS0oxxxxxxxx
- name: jack
user:
token: ldap:jack:123456
(2)运用kubectl进行测验
# kubectl get po --user=jack
Error from server (Forbidden): pods is forbidden: User "" cannot list resource "pods" in API group "" in the namespace "default"
webhook服务日志如下:
# ./kubernetes-auth-webhook
I1207 16:09:09.292067 7605 webhook.go:72] auth by ldap success
经过测验成果能够看到运用LDAP认证测验成功。
总结
运用Webhook能够很灵活的将K8S的租户和企业内部账户体系进行打通,这样能够方便办理用户账户。
不过上面开发的Webhook只是一个简单的比如,验证方式和方法都比较粗犷,CoreOS
开源的Dex
【2】是比较不错的产品,能够直接运用。
链接
【1】www.ldap.org.cn/286.html
【2】github.com/dexidp/dex
【3】项目地址:gitee.com/coolops/kub…