基于 client-go 实现 ServiceAccount 创建并实现 rolebind
前言:
在公司中由于采用的并非原生后台操作 K8S ,而是通过使用开源的 PAAS 平台向用户提供使用,但是有些业务部门需要通过 ServiceAccout(下面统称为 SA) 创建之后生成的 Secret 中的 ca.crt
以及 token
来实现对自身业务操作 K8S 。
并且在研发该工具之前一直都是通过手动认为创建的方式来实现该功能,但是有几次在同事创建好 SA 之后就给到了业务部门,结果发现创建出来的 SA 并不能正常使用,而且我们都知道通过命令的方式来进行 SA 的验证相对是麻烦的,比如需要将 ca.crt
以及 token
信息进行 base64 编码之后在调用 ApiServer 的地址才能够进行访问
并且该工具还需要将一个 SA 对多个 NS 下进行绑定:
内部需要对一个 K8S cluster 对外接入的 Token 进行精细化管控,就出现了不少需求侧要求对 K8S 内的多个 Namespace 使用各自独立 Token 管理和访问权限。
- 业务组 A:使用 TokenA 要对 Namespace A, B, C 控制
- 业务组 B:使用 TokenB 要对 Namespace E, F, G 控制
需求拆分
- 每一个业务组都有自己独立且唯一的 Token 访问 K8S
- 每一个业务组都有自己独立的需要管理和维护的多个 Namespace
- 每一个业务组都需要自己的 Namespace 与 自己的 Token 关联与绑定
- 每一个业务组的自有访问策略组内共享,业务组之间独立
概念解析
那么在 RBAC 的世界里面有下面几个重要的概念:
- Role: 针对特定 Namespace 内 apiGroup 设定访问控制规则
- RoleBinding: 针对特定 Namespace 内,将 Role 或 ClusterRole 与某一个 ServiceAccount 或者其他账号绑定
- ClusterRole: K8S 范围内 apiGroup 设定访问控制规则
- ClusterRoleBinding: K8S 范围内,将 ClusterRole 与某一个 ServiceAccount 或者其他账号绑定
这里有几个关键知识点要提及
- Role RoleBinding: 是针对某一个特定的 Namespace 做约束
- ClusterRole ClusterRoleBinding: 是 cluster 范围内做约束,不受某一个 Namespace 影响
- K8S Api Group: 分为 Namespace 级和非 Namespace 级。(这里很重要)
- RoleBinding 可以绑定 Role 和 ClusterRole,而 ClusterRoleBinding 只能绑定 ClusterRole
完整项目地址:https://github.com/As9530272755/Tool-set/tree/master/sactl
工具设计:
对于该工具我的设计功能有以下几点
- 能够验证 SA 是否创建成功
- 命令使用格式如下
sactl check -n NS SaName
从而通过 sa 获取到对应的资源信息,这里我暂定为获取 Pod 信息
- 命令使用格式如下
- 通过子命令自动创建 SA
- 命令使用格式如下:
sactl create -n ns SaName
同时调用 kubectl create rolebinding 命令,默认传参实现 SA 的创建同时也将该 sa 与 对应的 admin role 进行 bind,admin(该 role 为 PAAS 平台在页面创建 NS 的时候自动创建的一个 role,拥有该 NS 下所有权限)
- 命令使用格式如下:
- 需要链接 K8S 配置、日志系统、以及命令行 ctl
功能开发:
1 目录结构和 cobra 使用
[16:08:45 root@go sactl]#tree
.
├── cmd
│ ├── check.go
│ ├── create.go
│ └── root.go
├── config
│ └── config.go
├── controller
│ ├── check.go
│ └── create.go
├── etc
│ ├── config
│ └── saConfig.yaml
├── go.mod
├── go.sum
├── LICENSE
├── linK8S
│ └── linkK8s.go
├── log
├── logs
│ └── logs.go
├── main.go
└── model
└── SA_Role.go
可以看到有一下几个目录
- cmd 主要实现命令行工具的调用代码
- config 主要实现配置文件代码
- controller 主要处理核心逻辑代码
- etc 主要存放配置文件
- linK8S 链接K8S
- log 存放日志
- logs 编写日志处理代码
- model 模块代码
1.通过 cobra 命令创建 sactl 命令行工具, cobra init
命令来初始化 CLI 应用的脚手架:
$ cobra init --pkg-name sactl
2.初始化之后就会生成下面的目录结构
$ tree .
.
├── LICENSE
├── cmd
│ └── root.go
├── go.mod
├── go.sum
└── main.go
1 directory, 5 files
cobra 对应的用法参考我以前写的文章 http://39.105.137.222:8089/?p=1974#header-id-3
2 model 模块开发
该模块主要是用于在创建 SA 以及 RoleBind 的时候使用
package model
type SA struct {
NameSpace string
Name string
API string
}
func NewSa(namespace, name, api string) *SA {
return &SA{
NameSpace: namespace,
Name: name,
API: api,
}
}
type RoleBind struct {
NameSpace string
Name string
}
func NewRoleBind(namespace, name string) *RoleBind {
return &RoleBind{
namespace,
name,
}
}
type Secrets struct {
Token string
Namespace string
API string
}
func NewSecrets(namespace, api, token string) *Secrets {
return &Secrets{
Token: token,
Namespace: namespace,
API: api,
}
}
type AddRole struct {
Role string
RoleBind string
Namespace []string
}
func NewAddRole(roleBind, role string, namespace []string) *AddRole {
return &AddRole{
role,
roleBind,
namespace,
}
}
3 config 模块开发
我们先开发 config 模块然后就可以通过配置对应的功能获取到 K8S config 之后在链接 K8S 进行操作
在该模块中使用到了 viper
包,该包的使用我也写过对应的文章开一点击该链接,viper 使用
1.编写代码
// 该模块主要功能就是定义日志并放回 K8S 的 config 路径
package config
import (
"github.com/spf13/viper"
)
// 用于获取 K8S config
type KubeConfig struct {
ConfigPath string `mapstructure:"path"`
}
// 用于定义日志详细信息
type Log struct {
FileName string `mapstructure:"filename"`
Max_age int `mapstructure:"max_age"`
Max_size int `mapstructure:"max_size"`
Compress bool `mapstructure:"compress"`
Level string `mapstructure:"level"`
}
// 结构体嵌套从而直接返回 *Optins
type Optins struct {
KubeConfig KubeConfig `mapstructure:"kubeconfig"`
Log Log `mapstructure:"log"`
}
// 指定配置文件路径,并返回 optins 结构体
func ParseConfig() (*Optins, error) {
conf := viper.New()
// 指定我们的配置文件路径
conf.SetConfigFile("./etc/saConfig.yaml")
if err := conf.ReadInConfig(); err != nil {
return nil, err
}
// 解析并返回
optins := &Optins{}
if err := conf.Unmarshal(&optins); err != nil {
return nil, err
}
return optins, nil
}
2.编写配置文件
kubeconfig:
# K8S config 路径
path: ./etc/config
log:
filename: log/sactl.log
max_age: 30
max_size: 10
max_backups: 14
compress: false
level: info
4 编写 logs 模块
这里编写 logs 功能,因为在该功能中有对应的 error 处理
该包的使用我也写过对应的文章开一点击该链接:logrus 日志处理工具
package logs
import (
"github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
"sactl/config"
)
// 记录日志
func Logs() {
// 调用 config.ParseConfig() ,返回 optins
optins, err := config.ParseConfig()
if err != nil {
ErrInfo("Logs optins", "fail", err)
}
logger := lumberjack.Logger{
// 基于 optins 来填写日志的信息
Filename: optins.Log.FileName,
MaxSize: optins.Log.Max_size,
MaxAge: optins.Log.Max_age,
Compress: optins.Log.Compress,
}
defer logger.Close()
// 放入我们通过配置文件中获取的设置,来生成日志信息
logrus.SetOutput(&logger)
// 转换日志级别
level, err := logrus.ParseLevel(optins.Log.Level)
if err != nil {
ErrInfo("log Level", "fail", err)
}
// 设置日志级别
logrus.SetLevel(logrus.Level(level))
logrus.SetReportCaller(true)
//logrus.SetFormatter(&logrus.JSONFormatter{})
}
// error 和 info 处理函数
func ErrInfo(action string, sample interface{}, err error) {
if err != nil {
logrus.WithFields(
logrus.Fields{
"Action": action,
"Sample": sample,
"Error_Info": err,
}).Error("SaCtl")
} else {
logrus.WithFields(
logrus.Fields{
"action": action,
"sample": sample,
"Info": err,
}).Info("SaCtl")
}
}
5 link_K8S 模块开发
在 config 中我们已经获取了对应的 K8S config 路径,该文件是链接 K8S 的核心文件,也就是说当我们有了该文件之后才能够链接 K8S
package linK8S
import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
// 获取配置文件中 K8S config 信息,返回 ClientSet
func Link_K8s(configPath string) (*kubernetes.Clientset, error) {
config, err := clientcmd.BuildConfigFromFlags("", configPath)
if err != nil {
return nil, err
}
clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
// 返回
return clientSet, nil
}
6 编写 controller 模块
该模块有一下两个功能一个是 check SA 是否正常,另外一个是 create SA 并实现 rolebind
6.1 check.go 模块
我们都知道在一个 SA 之后就会指定生成一个 secrets ,如下:
# 创建 sa
16:01:42 root@master test]#kubectl create sa test
serviceaccount/test created
# 生成 secrets
[17:03:36 root@master test]#kubectl get secrets
NAME TYPE DATA AGE
default-token-rd7kc kubernetes.io/service-account-token 3 2d1h
test-token-s7mt4 kubernetes.io/service-account-token 3 9s # 创建 SA 自动生成的 secrets
# 也就是我们需要获取他的 ca 和 token 才能够调用 K8S api 进行操作
[17:03:45 root@master test]#kubectl get secrets test-token-s7mt4 -o yaml
apiVersion: v1
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1ESXdOakEzTXprME5Gb1hEVE16TURJd016QTNNemswTkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBT1kvCk9qa0VFMTFra3I4TzJrdlFrd3ZUUTlsVjZ0aFRzSDBOcnU2Sk9YUVBKWXpOS0U3T1RDd0dUNnNUbVZLOUJveG4KNmREUTBEd29rYWxVdTVjZC9LV09lUHVHTlNpK3J5aHVXRWxGdlR4VndIQkk4akFua2JkWFdRZWFMRXZISFVVNApiYUlxSjlrTldXcWFMdXFWWmZuUWovL2pUSlVVNGFKYnpzcUhYU3M1K0RqQTcxOUJWY09FdFVqQXNKellQVHVqCnE4SzFyeXJkYitJdExXMkhYSkdPZEVxMTN4VkhVSHRnMVQveVNSeGFYbEFFN1NpTnh1MzluYUZKY3dLNmllc1QKQ2gzb1ZGejJLaFc0VUVWRVlnQlpXSUZMMWh3VkVaV1cxeXJiblQyNjVWU25OeWtKcjAxY1NNaHNyNVpyTWJmVworTkd2aXNWcldvZTJVZTVQdWtNQ0F3RUFBYU5aTUZjd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZJTnFBWXA1TU42bUFza2hyWW9ldjM3Y25CRXJNQlVHQTFVZEVRUU8KTUF5Q0NtdDFZbVZ5Ym1WMFpYTXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQTJQa3E5STFhMVZORmx2R3JsRQorNXVxUHExYnJFL0cweXEzMVpXSHRvdHJ3Q1poZmY5U1E0YzQ0cHYyVmZnMzBKQkN5N1FLS0dZV0ZvS29xeG8rCmhobFAzd05HMmp2WWFTem82TGV2N01RMEFTdktmcklsZUJxbnFWdmNON1VpajFpK1ZrMC9nM2hIbXV1akdmckUKS0pwMFVaSTk5VjZZUUJCUUtDTjcvVjNXQ3ptbkIxOWtuT2ZnNGdtSXJTRVBqa29XbUd6d3VzTUc2UERmTnNVVwppU2hpb1BHa2FqNVZaam9TL0FtTFpzb1lwQlNKVFZQcnY3MGdzUXErQ3U5K2ZBczJ3WkNjZG81b3Jwa3RxQU5SCmtSNk9zQS9tNmd6c1lQczZLc3dCc0llUml6L3dzWnNXWXNkWjJQcWViY2lkQjl0bm80Znd0d3pXTFNMRU5YNmsKcitFPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
namespace: ZGVmYXVsdA==
token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklreGxYMkpSV0VGRE56UkNObUZvZEhwMlZ6WkJhbkJ3TUZsRFlXeFlZazFqTVMxQ1ZqTXlWM2xmT1hNaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJblJsYzNRdGRHOXJaVzR0Y3pkdGREUWlMQ0pyZFdKbGNtNWxkR1Z6TG1sdkwzTmxjblpwWTJWaFkyTnZkVzUwTDNObGNuWnBZMlV0WVdOamIzVnVkQzV1WVcxbElqb2lkR1Z6ZENJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZ5ZG1salpTMWhZMk52ZFc1MExuVnBaQ0k2SWpJMVltRTBZMkZqTFRjME9UWXRORGcxT1MwNE1ESmpMVEUzT0ROaE1XVXlaRGN3TUNJc0luTjFZaUk2SW5ONWMzUmxiVHB6WlhKMmFXTmxZV05qYjNWdWREcGtaV1poZFd4ME9uUmxjM1FpZlEuRXhjcVdBUXVxYkswS1dhME5zN2I4TVBKT08xVzFaWUtEdTFjcFZmRWtlb21mRUY4dXVTNHFuR2R5T2t5MG5NN0NnQTM1X29sS0szVUdlWkVYckltMjNsNC1sQUxneW5WY0FHeng4LXhZMTRueHp1NUlfa3ZOVHdGWHRvSTl1VGpWalBqb1dzYmJlMmtaOHhRQXdsQkY1QXVFWTdWbS1hUV95aTRHMTd0cWVnMlNiMHNtaUQxRWtuM0p3MTNpalpGODI1UmJIajJ2M0FHNEJrN0duWENpdlljdFYtek9XaTFuSFk4MllCVks4ZlZqOUppOTJaME1xdUNmcEVmbThfT1Q4bUtCWWhtdU90U2pBdmZlS0o1OERCUENva3Q0RlNIb0l0UDA2Z1F1cGJ0NU5OTk5td2piZWI2MTBQVlZZUktGdEVDTUhKbE5KMTg1V0VMaHdSUnlR
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: test
kubernetes.io/service-account.uid: 25ba4cac-7496-4859-802c-1783a1e2d700
creationTimestamp: "2023-02-08T09:03:36Z"
name: test-token-s7mt4
namespace: default
resourceVersion: "106017"
uid: 31b50b40-fe68-416f-a1a8-c3674f1449f2
type: kubernetes.io/service-account-token
编写代码
package controller
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sactl/config"
"sactl/linK8S"
"sactl/logs"
"sactl/model"
"time"
)
// 定义下面需要用到的变量,CA Token Secrets
var (
CA []byte
Token []byte
ClientSet *kubernetes.Clientset
Secrets string
)
// 该函数用于获取 secrets , newSA 是通过 cobra 的 cmd 中进行传参得到
func Get_Secrets(newSA *model.SA) {
// 基于 config 获取到对应的 K8S config 路径
config, err := config.ParseConfig()
if err != nil {
fmt.Println("Error from server Config:", err)
logs.ErrInfo("GetSecrets", "Config Fail", err)
}
// 并得到 clientSet
ClientSet, err = linK8S.Link_K8s(config.KubeConfig.ConfigPath)
if err != nil {
fmt.Println("Error from server ClientSet:", err)
logs.ErrInfo("GetSecrets", "ClientSet", err)
}
// 获取对应的 SA 信息,并且返回一个 []
saSet := ClientSet.CoreV1().ServiceAccounts(newSA.NameSpace)
saList, err := saSet.List(context.TODO(), metav1.ListOptions{})
if err != nil {
fmt.Println("Error from server SAList:", err)
logs.ErrInfo("GetSecrets 函数", "saList 报错", err)
}
// for 判断 sa.Name 是否和 nweSA.NAME 相同,如果相同那么就是我们在 cmd 中需要查找的 SA
for _, sa := range saList.Items {
if sa.Name == newSA.Name {
// 索引[0] 就是对应 secrets,并赋值给 Secrets
Secrets = sa.Secrets[0].Name
}
}
}
// 获取到了 Secrets 之后就需要获取 token 和 CA
func Get_Token(newSA *model.SA) {
// 获取指定的 Namespace 下的 SecretsSet
secretsSet := ClientSet.CoreV1().Secrets(newSA.NameSpace)
// 获取 Secrets
se, err := secretsSet.Get(context.TODO(), Secrets, metav1.GetOptions{})
if err != nil {
fmt.Println("Error from server Get_Token:", err)
logs.ErrInfo("Get_Token", "Secrets", err)
}
// se.Data 是一个 map 类型,所以这里通过 key-value 的方式来获取对应的 CA 和 token
CA = se.Data["ca.crt"]
Token = se.Data["token"]
}
// 通过 token 和 CA 获取资源用于验证
func Get_RS(newSA *model.SA) {
// 通过CA 来获取 client
tlsClientConfig := rest.TLSClientConfig{
CAData: CA,
}
// 通过 SA 的方式来创建 config
config := rest.Config{
Host: newSA.API,
BearerToken: string(Token),
TLSClientConfig: tlsClientConfig,
Timeout: 20 * time.Second,
}
// 检查是否链接 K8S
RSclientSet, err := kubernetes.NewForConfig(&config)
if err != nil {
fmt.Println("Error from server (NotFound) 基于 token CA 检查失败:", err)
logs.ErrInfo("GetRS", "基于 token CA 检查失败", err)
}
// 如果链接成功获取对应的 NS 下的 POD
pods := RSclientSet.CoreV1().Pods(newSA.NameSpace)
podsList, err := pods.List(context.TODO(), metav1.ListOptions{})
if err != nil {
fmt.Println("Error from server (NotFound):", err)
logs.ErrInfo("GetRS podsList", "基于 token CA 获取 POD 失败", err)
}
// 将其全部输出
for _, pod := range podsList.Items {
fmt.Printf("NameSpace:%s\tPod:%s\n", pod.Namespace, pod.Name)
}
}
以上就是 check 功能的开发
6.2 create.go 模块
现在我们开始编写创建 sa 并实现 rolebind 功能
package controller
import (
"context"
"fmt"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
config2 "sactl/config"
"sactl/linK8S"
"sactl/logs"
"sactl/model"
)
// create ServiceAccount
// 基于通过 cobra 的 cmd 中进行传参得到 newSa
func Create_SA(newSa *model.SA) {
// 获取 K8S client SET
config, err := config2.ParseConfig()
if err != nil {
logs.ErrInfo("Create_SA", "create SA config error!", err)
}
clientSet, err := linK8S.Link_K8s(config.KubeConfig.ConfigPath)
if err != nil {
logs.ErrInfo("Create_SA", "create SA clientSet error!", err)
}
// 创建 SA
sa, err := clientSet.CoreV1().ServiceAccounts(newSa.NameSpace).Create(context.TODO(), &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: newSa.Name,
Namespace: newSa.NameSpace,
},
}, metav1.CreateOptions{})
if err != nil {
logs.ErrInfo("Create_SA", "创建 SA Create 时操作失败", err)
fmt.Println("Failed to create ServiceAccounts!\n", err)
} else {
fmt.Printf("%v ServiceAccounts Create Success!\n", sa.Name)
}
}
// kubectl create sa and rolebind admin role
func Role_Bind(newRoleBind *model.RoleBind) {
config, err := config2.ParseConfig()
if err != nil {
logs.ErrInfo("Create_SA", "创建 SA config 位置失败", err)
}
clientSet, err := linK8S.Link_K8s(config.KubeConfig.ConfigPath)
if err != nil {
logs.ErrInfo("Create_SA", "创建 SA clientSet 位置失败", err)
}
// 创建 rolebind
roleBind, err := clientSet.RbacV1().RoleBindings(newRoleBind.NameSpace).Create(context.TODO(), &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: newRoleBind.Name,
Namespace: newRoleBind.NameSpace,
},
Subjects: []rbacv1.Subject{
{Kind: "ServiceAccount",
Name: newRoleBind.Name,
Namespace: newRoleBind.NameSpace},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: "admin", // 这里我写死为 admin role 后期如果需要自定义即可自行更改
},
}, metav1.CreateOptions{})
if err != nil {
logs.ErrInfo("roleBind", "create roleBind error!", err)
fmt.Println("Failed to create roleBind!\n", err)
} else {
fmt.Printf("%v Rolebinding Create Success!\n", roleBind.Name)
}
}
6.3 token_get.go 模块开发
该功能不再需要 client 通过 K8S config 链接,而是可以通过 SA 所生成的 secrets 中的 token 进行链接即可
package controller
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"sactl/logs"
"sactl/model"
)
// 通过 cmd 模块中的 get.go 获取到用户输入的 secrets 结构体
func TokenGet(newSecrets *model.Secrets) {
// 通过用户输入 token 获取
token := newSecrets.Token
// 基于 rest 链接,得到 rc
rc := &rest.Config{
// API 用户输入
Host: newSecrets.API,
// 开启 TLS 认证
TLSClientConfig: rest.TLSClientConfig{
Insecure: true,
},
// BearerToken 用户输入
BearerToken: token,
}
// 基于 rc 链接并得到,cs 就是一个 clientSet
cs, err := kubernetes.NewForConfig(rc)
if err != nil {
logs.ErrInfo("cs 连接失败", "kubernetes.NewForConfig(rc)", err)
}
// 基于不同资源选择
//if newSecrets.Rs == "pod" {
// pod, err := cs.CoreV1().Pods(newSecrets.Namespace).List(context.TODO(), metav1.ListOptions{})
// if err != nil {
// fmt.Println("token 验证失败!")
// } else {
// fmt.Println("token 验证成功!")
// }
// for _, p := range pod.Items {
// fmt.Println(p.Namespace, p.Name)
// }
//} else if newSecrets.Rs == "deployment" {
// dels, err := cs.AppsV1().Deployments(newSecrets.Namespace).List(context.TODO(), metav1.ListOptions{})
// if err != nil {
// logs.ErrInfo("del", "errinfo ", err)
// }
// for _, del := range dels.Items {
// fmt.Println(del.Name)
// }
//} else {
// fmt.Println("输入资源错误")
//}
// 获取 POD
pod, err := cs.CoreV1().Pods(newSecrets.Namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
logs.ErrInfo("TokenGet token pod 验证", "cs.corev1.pod", err)
fmt.Println("token 验证失败!")
return
} else {
fmt.Println("token 验证成功!")
}
for _, p := range pod.Items {
fmt.Printf("namespace:%s\tpod:%s\n", p.Namespace, p.Name)
}
}
6.4 add_rolebind.go 模块
该模块主要想实现的功能就是在现有的 SA 上将其添加到别的 NS 中进行 RoleBindg 操作,从而实现单个 SA 对多个 NS 进行纳管
package controller
import (
"context"
"fmt"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
config2 "sactl/config"
"sactl/linK8S"
"sactl/logs"
"sactl/model"
)
// kubectl create sa and rolebind role
func Add_RoleBind(new_RoleBindg *model.AddRole) {
config, err := config2.ParseConfig()
if err != nil {
logs.ErrInfo("Create_SA", "创建 SA config 位置失败", err)
}
clientSet, err := linK8S.Link_K8s(config.KubeConfig.ConfigPath)
if err != nil {
logs.ErrInfo("Add_RoleBind", "创建 Add_RoleBind clientSet 位置失败", err)
}
// 遍历用户输入的多个 NS
for i := 0; i < len(new_RoleBindg.Namespace); i++ {
// 判断是否输入的 role 已经存在,如果为空就表示 ns 下不存在对应的 role,并且进入下一个循环
_, err := clientSet.RbacV1().Roles(new_RoleBindg.Namespace[i]).Get(context.TODO(), new_RoleBindg.Role, metav1.GetOptions{})
if err != nil {
logs.ErrInfo(new_RoleBindg.Role, new_RoleBindg.Namespace[i], err)
fmt.Printf("错误! namespace:%s 下不存在 role:%s!\n", new_RoleBindg.Namespace[i], new_RoleBindg.Role)
continue
// 如果用户输入的 role 存在那么就判断 rolebind 是否存在,如果 rolebind 不存在那么就执行并创建 rolebind
} else if _, err := clientSet.RbacV1().RoleBindings(new_RoleBindg.Namespace[i]).Get(context.TODO(), new_RoleBindg.RoleBind, metav1.GetOptions{}); err != nil {
if err != nil {
rolebind, err := clientSet.RbacV1().RoleBindings(new_RoleBindg.Namespace[i]).Create(context.TODO(), &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: new_RoleBindg.RoleBind,
Namespace: new_RoleBindg.Namespace[i],
},
Subjects: []rbacv1.Subject{
{Kind: "ServiceAccount",
Name: new_RoleBindg.RoleBind,
Namespace: "kube-system"}, // 指定写死为 kube-system NS 后期手动更改源码
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: new_RoleBindg.Role,
},
}, metav1.CreateOptions{})
if err != nil {
logs.ErrInfo("roleBind", "create roleBind error!", err)
fmt.Println("Failed to create roleBind!\n", err)
return
} else {
fmt.Printf("%v Rolebinding Create Success!\n", rolebind.Name)
}
}
// 否则提示用户 rolebind 已经存在
} else {
logs.ErrInfo(new_RoleBindg.Role, new_RoleBindg.Namespace[i], err)
fmt.Printf("错误! namespace:%s 下已存在 RoleBindg:%s!\n", new_RoleBindg.Namespace[i], new_RoleBindg.RoleBind)
continue
}
}
}
7 编写 cmd 模块
该模块主要是用到 cobra 包
cobra 对应的用法参考我以前写的文章 http://39.105.137.222:8089/?p=1974#header-id-3
7.1 check.go 模块
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"sactl/controller"
"sactl/model"
)
// checkCmd represents the check command
var checkCmd = &cobra.Command{
Use: `check`,
Short: "该子命令用于验证 SA",
Long: `验证示例:
sactl check -n namespace sa api`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 || len(args) != 3 {
fmt.Println(`语法错误:
查看帮助: sactl check -h `)
return
}
// 判断是否需要 -n 指定 NS
fstatus, _ := cmd.Flags().GetBool("namespace")
if fstatus {
appoint_NS(args)
} else {
check_SA(args)
}
},
}
func init() {
rootCmd.AddCommand(checkCmd)
checkCmd.Flags().BoolP("namespace", "n", false, "指定 namespace 验证 SA,默认情况下访问 default NS")
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// checkCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// checkCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
// 接收用户输入的 args
func check_SA(args []string) {
// 如果不输 -n ns 就默认在 default 下,并通过工厂函数初始化 NewSA 结构体
newSA := model.NewSa("default", args[0], args[1])
// 传入 newSA
controller.Get_Secrets(newSA)
controller.Get_Token(newSA)
controller.Get_RS(newSA)
}
// 接收用户输入的 args ,该函数用于 -n 指定 NS 时使用
func appoint_NS(args []string) {
newSA := model.NewSa(args[0], args[1], args[2])
controller.Get_Secrets(newSA)
controller.Get_Token(newSA)
controller.Get_RS(newSA)
}
7.2 create.go 模块
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"sactl/controller"
"sactl/model"
"github.com/spf13/cobra"
)
// createCmd represents the create command
var createCmd = &cobra.Command{
Use: "create",
Short: "该子命令用于创建 SA",
Long: `验证示例:
sactl create -n namespace sa`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 || len(args) != 2 {
fmt.Println(`语法错误:
查看帮助: sactl create -h `)
return
}
fstatus, _ := cmd.Flags().GetBool("namespace")
if fstatus {
Create_Sa_Rolebind(args)
} else {
fmt.Println(`语法错误:
查看帮助: sactl create -h `)
return
}
},
}
func init() {
rootCmd.AddCommand(createCmd)
createCmd.Flags().BoolP("namespace", "n", false, "指定 namespace 创建 SA,实现 rolebind")
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// createCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// createCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
func Create_Sa_Rolebind(args []string) {
newSA := model.NewSa(args[0], args[1], "")
newRoleBind := model.NewRoleBind(args[0], args[1])
controller.Create_SA(newSA)
controller.Role_Bind(newRoleBind)
}
7.3 get.go 模块
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"sactl/controller"
"sactl/model"
"github.com/spf13/cobra"
)
// getCmd represents the get command
var getCmd = &cobra.Command{
Use: "get",
Short: "该子命令用于验证 Token 可用性!",
Long: `验证示例:
sactl get -n namespace api token`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 || len(args) != 3 {
fmt.Println(`语法错误:
查看帮助: sactl get -h `)
return
}
fstatus, _ := cmd.Flags().GetBool("namespace")
if fstatus {
get_Secrets(args)
} else {
fmt.Println(`语法错误:
查看帮助: sactl get -h `)
return
}
},
}
func init() {
rootCmd.AddCommand(getCmd)
getCmd.Flags().BoolP("namespace", "n", false, "指定 namespace 实现基于 token 认证")
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// getCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// getCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
func get_Secrets(args []string) {
namespace := args[0]
api := args[1]
token := args[2]
secrets := model.NewSecrets(namespace, api, token)
controller.TokenGet(secrets)
}
7.4 add.go 模块
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"sactl/controller"
"sactl/logs"
"sactl/model"
)
var (
role string
)
// addCmd represents the add command
var addCmd = &cobra.Command{
Use: "add",
Short: "该子命令用于 SA 绑定不同 namespace 下的 RoleBind!",
Long: `验证示例:
sactl add ServiceAccount -r RoleBind -n NameSpace1 NameSpace2 NameSpace3 ... `,
Run: func(cmd *cobra.Command, args []string) {
//if len(args) == 0 {
// fmt.Println(`语法错误:
//查看帮助: sactl add -h `)
// return
//}
//
fstatus, err := cmd.Flags().GetBool("namespace")
if fstatus {
AddSa(args)
} else {
fmt.Printf("Error:namesapce 未指定\nsactl add -h 查看帮助!\n")
logs.ErrInfo("cmd error", "AddSa", err)
return
}
},
}
func init() {
rootCmd.AddCommand(addCmd)
// 这里由于需要传入多个参数所以通过 StringVarP() 函数,接收 namespace 和 role
addCmd.Flags().StringVarP(&role, "role", "r", "", "指定 ServiceAccount 绑定的 Role")
addCmd.Flags().BoolP("namespace", "n", false, "指定 Role 创建的 NameSpace")
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// addCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
func AddSa(args []string) {
roleBind := args[0]
namespaces := args[1:]
addSa := model.NewAddRole(roleBind, role, namespaces)
controller.Add_RoleBind(addSa)
}
8 main.go
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"sactl/cmd"
"sactl/logs"
)
func main() {
// 先实现日志以及 config 功能的配置
logs.Logs()
cmd.Execute()
}
9 验证
1.创建 ns 和 pod
[17:30:06 root@master test]#kubectl create ns dev
namespace/dev created
[17:30:25 root@master test]#kubectl run nginx --image=nginx:1.16 --namespace=dev
pod/nginx created
[17:32:53 root@master test]#kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx 0/1 ContainerCreating 0 2m20s
9.1 验证 role 绑定 admin
1.创建一个 admin 的 role ,因为刚才在代码中写死了需要绑定 role 为 admin
[17:30:35 root@master test]#cat test_role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: admin # 指定 role 为 admin
rules:
- apiGroups: ["*"] # "" 标明 core API 组以及所有资源和操作的权限, * 表示所有
resources: ["*"]
verbs: ["*"]
[17:32:55 root@master test]#kubectl apply -f test_role.yaml
role.rbac.authorization.k8s.io/admin created
2 通过程序创建 sa
[16:30:02 root@go sactl]#go run main.go create -n dev sa1
sa1 ServiceAccounts Create Success!
sa1 Rolebinding Create Success!
9.2 验证基于 SA 获取 POD
1 通过程序来获取 pod
[17:34:07 root@go sactl]#go run main.go check -n dev sa1 "https://10.0.0.131:6443"
NameSpace:dev Pod:nginx
# 获取成功
9.3 验证基于 Token 获取 Pod 信息
通过 token 获取到对应的 Pod 信息
1.获取 token
# 获取 sa
[15:57:47 root@master ~]#kubectl get sa -n dev
NAME SECRETS AGE
default 1 22h
sa1 1 22h
# 获取 role
[15:58:00 root@master ~]#kubectl get role -n dev
NAME CREATED AT
admin 2023-02-08T09:34:22Z
# 查看 secrets
[15:58:13 root@master ~]#kubectl get secrets -n dev
NAME TYPE DATA AGE
default-token-9bmqc kubernetes.io/service-account-token 3 22h
sa1-token-c5frn kubernetes.io/service-account-token 3 22h
# 查看 rolebind
[15:58:22 root@master ~]#kubectl get rolebindings.rbac.authorization.k8s.io -n dev
NAME ROLE AGE
sa1 Role/admin 22h
# 查看 POD
[15:58:31 root@master ~]#kubectl get pod -n dev
No resources found in dev namespace.
# 创建 POD
[15:58:39 root@master ~]#kubectl run nginx --image=nginx:1.16 -n dev
pod/nginx created
# 获取 pod
[15:58:56 root@master ~]#kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
nginx 0/1 ContainerCreating 0 4s
# 查看 token
[15:59:00 root@master ~]#kubectl describe secrets -n dev sa1-token-c5frn
Name: sa1-token-c5frn
Namespace: dev
Labels: <none>
Annotations: kubernetes.io/service-account.name: sa1
kubernetes.io/service-account.uid: f12b5cbd-3444-4fd7-ac86-7fa877aa0d87
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1099 bytes
namespace: 3 bytes
# 下面就是获取到的 token 信息
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IkxlX2JRWEFDNzRCNmFodHp2VzZBanBwMFlDYWxYYk1jMS1CVjMyV3lfOXMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXYiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoic2ExLXRva2VuLWM1ZnJuIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InNhMSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImYxMmI1Y2JkLTM0NDQtNGZkNy1hYzg2LTdmYTg3N2FhMGQ4NyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZXY6c2ExIn0.W0iIh7gn3ajuUGN15TvdwDMA3NhhDw-OflrFqZmw6Cvj3_V1kB1o-o1bvgPOdpf758rvSITg96zHSmHguZaRpmUnG7NsEvBHO78hpmzThcWLY8-jcP69nfyeRwzF2M7V3oZ9KyACi1pF5DvYhoUFgpBQ9bIgSGPRlwtM-0YbPOldwPCbImEomHtQhuTZ_tAqm43-xc_jnfnm1kXjP-zhWoq_a_SD28hL_G3CunrMoYajc0VLVcLPIAu53qAHDGp4XQX0Zwo2NQYhU2EGLT4mN7dBZKq36VdS8rTjUegke9dH9zLa6chZUZPX_q6LsW_QfQBK4s84AFdVjnBeAIL7Bg
2.工具基于 token 访问 pod
# token 可以看到是相同的
[17:31:57 root@go sactl]#go run main.go get -n dev "https://10.0.0.131:6443" "eyJhbGciOiJSUzI1NiIsImtpZCI6IkxlX2JRWEFDNzRCNmFodHp2VzZBanBwMFlDYWxYYk1jMS1CVjMyV3lfOXMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXYiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoic2ExLXRva2VuLWM1ZnJuIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InNhMSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImYxMmI1Y2JkLTM0NDQtNGZkNy1hYzg2LTdmYTg3N2FhMGQ4NyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZXY6c2ExIn0.W0iIh7gn3ajuUGN15TvdwDMA3NhhDw-OflrFqZmw6Cvj3_V1kB1o-o1bvgPOdpf758rvSITg96zHSmHguZaRpmUnG7NsEvBHO78hpmzThcWLY8-jcP69nfyeRwzF2M7V3oZ9KyACi1pF5DvYhoUFgpBQ9bIgSGPRlwtM-0YbPOldwPCbImEomHtQhuTZ_tAqm43-xc_jnfnm1kXjP-zhWoq_a_SD28hL_G3CunrMoYajc0VLVcLPIAu53qAHDGp4XQX0Zwo2NQYhU2EGLT4mN7dBZKq36VdS8rTjUegke9dH9zLa6chZUZPX_q6LsW_QfQBK4s84AFdVjnBeAIL7Bg"
token 验证成功!
namespace:dev pod:nginx
9.4 追加 RoleBind
验证将 kube-system 绑定到多个 NS 下
# 先在 kube-systeml 中创建一个 sa1
[16:54:17 root@master test]#kubectl get sa -n kube-system sa1
NAME SECRETS AGE
sa1 1 30m
# 所有集群下没有 admin1 role
[17:00:07 root@master test]#kubectl get role -A| grep admin1
# 所以程序未能找到对应的 role 无法做后续的 rolebind 动作
[16:52:19 root@go sactl]#go run main.go add sa1 -r admin1 -n test dev temp
错误! namespace:test 下不存在 role:admi1n!
错误! namespace:dev 下不存在 role:admi1n!
错误! namespace:temp 下不存在 role:admi1n!
# 可以看到 dev temp test 这三个 NS 有 role admin
[17:01:43 root@master test]#kubectl get role -A| grep admin
dev admin 2023-02-08T09:34:22Z
temp admin 2023-02-10T06:17:01Z
test admin 2023-02-10T02:03:26Z
# 这时候我将 kube-system ns 下的 sa1 绑定到这三个 NS 下的 admin role 上
[16:58:17 root@go sactl]#go run main.go add sa1 -r admin -n test dev temp
sa1 Rolebinding Create Success!
sa1 Rolebinding Create Success!
sa1 Rolebinding Create Success!
# 验证已经绑定成功
[17:03:12 root@master test]#kubectl get rolebindings.rbac.authorization.k8s.io -A | grep sa1
dev sa1 Role/admin 20s
temp sa1 Role/admin 20s
test sa1 Role/admin 20s
# 再次创建 sa1 rolebind 发现程序识别错误并退出
[16:58:03 root@go sactl]#go run main.go add sa1 -r admin -n test dev temp
错误! namespace:test 下已存在 RoleBindg:sa1!
错误! namespace:dev 下已存在 RoleBindg:sa1!
错误! namespace:temp 下已存在 RoleBindg:sa1!
总结:
该工具需要我们了解 K8S 中的 sa secrets 的关联关系,以及 role rolebind 的关系,和 client-go sdk 的使用