什么是Authorizer Authorizer负责Kubernetes中对请求进行授权,只有通过授权的请求才会被执行。在Kubernetes中,主要有ABAC(Attributes Based Access Control),RBAC(Role Based Access Contro),AlwaysAllow,AlwaysDeny和Webhook(调用网络接口进行授权)这几种授权器。本次分析将介绍Kubernetes是如何管理授权器的,及如何对请求进行授权。
授权器的生成 和认证器一样,授权器在/cmd/kube-apiserver/app/server.go的Run()中生成:1
2
3
4
5
6
7
8
9
10
11
12
13
14
authorizationConfig := authorizer.AuthorizationConfig{
PolicyFile: s.GenericServerRunOptions.AuthorizationPolicyFile,
WebhookConfigFile: s.GenericServerRunOptions.AuthorizationWebhookConfigFile,
WebhookCacheAuthorizedTTL: s.GenericServerRunOptions.AuthorizationWebhookCacheAuthorizedTTL,
WebhookCacheUnauthorizedTTL: s.GenericServerRunOptions.AuthorizationWebhookCacheUnauthorizedTTL,
RBACSuperUser: s.GenericServerRunOptions.AuthorizationRBACSuperUser,
InformerFactory: sharedInformers,
}
authorizationModeNames := strings.Split(s.GenericServerRunOptions.AuthorizationMode, "," )
apiAuthorizer, err := authorizer.NewAuthorizerFromAuthorizationConfig(authorizationModeNames, authorizationConfig)
if err != nil {
glog.Fatalf("Invalid Authorization Config: %v" , err)
}
在kube-apiserver的参数中,有–authorization-mode参数,授权器就是根据–authorization-mode参数中指定的内容来生成的。来看下NewAuthorizerFromAuthorizationConfig()函数,定义在/pkg/genericapiserver/authorizer/authz.go中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
func NewAuthorizerFromAuthorizationConfig (authorizationModes []string , config AuthorizationConfig) (authorizer.Authorizer, error) {
if len (authorizationModes) == 0 {
return nil , errors.New("At least one authorization mode should be passed" )
}
var authorizers []authorizer.Authorizer
authorizerMap := make (map [string ]bool )
for _, authorizationMode := range authorizationModes {
if authorizerMap[authorizationMode] {
return nil , fmt.Errorf("Authorization mode %s specified more than once" , authorizationMode)
}
switch authorizationMode {
case options.ModeAlwaysAllow:
authorizers = append (authorizers, NewAlwaysAllowAuthorizer())
case options.ModeAlwaysDeny:
authorizers = append (authorizers, NewAlwaysDenyAuthorizer())
case options.ModeABAC:
if config.PolicyFile == "" {
return nil , errors.New("ABAC's authorization policy file not passed" )
}
abacAuthorizer, err := abac.NewFromFile(config.PolicyFile)
if err != nil {
return nil , err
}
authorizers = append (authorizers, abacAuthorizer)
case options.ModeWebhook:
if config.WebhookConfigFile == "" {
return nil , errors.New("Webhook's configuration file not passed" )
}
webhookAuthorizer, err := webhook.New(config.WebhookConfigFile,
config.WebhookCacheAuthorizedTTL,
config.WebhookCacheUnauthorizedTTL)
if err != nil {
return nil , err
}
authorizers = append (authorizers, webhookAuthorizer)
case options.ModeRBAC:
rbacAuthorizer := rbac.New(
config.InformerFactory.Roles().Lister(),
config.InformerFactory.RoleBindings().Lister(),
config.InformerFactory.ClusterRoles().Lister(),
config.InformerFactory.ClusterRoleBindings().Lister(),
config.RBACSuperUser,
)
authorizers = append (authorizers, rbacAuthorizer)
default :
return nil , fmt.Errorf("Unknown authorization mode %s specified" , authorizationMode)
}
authorizerMap[authorizationMode] = true
}
if !authorizerMap[options.ModeABAC] && config.PolicyFile != "" {
return nil , errors.New("Cannot specify --authorization-policy-file without mode ABAC" )
}
if !authorizerMap[options.ModeWebhook] && config.WebhookConfigFile != "" {
return nil , errors.New("Cannot specify --authorization-webhook-config-file without mode Webhook" )
}
if !authorizerMap[options.ModeRBAC] && config.RBACSuperUser != "" {
return nil , errors.New("Cannot specify --authorization-rbac-super-user without mode RBAC" )
}
return union.New(authorizers...), nil
}
NewAuthorizerFromAuthorizationConfig()会依据authorizationModes生成相应的authorizer,并把多个authorizers打包成unionAuthorizer返回。
最后server.go中生成的apiAuthorizer会赋值给genericConfig.Authorizer以生成master。1
genericConfig.Authorizer = apiAuthorizer
unionAuthorizer unionAuthorizer定义在/pkg/auth/authorizer/union/union.go中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type unionAuthzHandler []authorizer.Authorizer
func New (authorizationHandlers ...authorizer.Authorizer) authorizer .Authorizer {
return unionAuthzHandler(authorizationHandlers)
}
func (authzHandler unionAuthzHandler) Authorize (a authorizer.Attributes) (bool , string , error) {
var (
errlist []error
reasonlist []string
)
for _, currAuthzHandler := range authzHandler {
authorized, reason, err := currAuthzHandler.Authorize(a)
if err != nil {
errlist = append (errlist, err)
}
if len (reason) != 0 {
reasonlist = append (reasonlist, reason)
}
if !authorized {
continue
}
return true , reason, nil
}
return false , strings.Join(reasonlist, "\n" ), utilerrors.NewAggregate(errlist)
}
unionAuthorizer本身是一个Authorizer列表,其Authorize()方法会调用列表中每一个Authorizer的Authorize()方法,一旦有一个Authorizer授权通过,则unionAuthorizer的授权通过。
ABAC授权器 接下来,我们来看下ABAC,ABAC是基于属性的访问控制,目前ABAC支持apiVersion, kind, spec, namespace, resource等属性。ABAC规则需要保存在文件中(存在ETCD中在开发中):1
2
3
4
5
6
7
8
9
10
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"group" :"system:authenticated" , "nonResourcePath" : "*" , "readonly" : true }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"group" :"system:unauthenticated" , "nonResourcePath" : "*" , "readonly" : true }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"user" :"admin" , "namespace" : "*" , "resource" : "*" , "apiGroup" : "*" }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"user" :"scheduler" , "namespace" : "*" , "resource" : "pods" , "readonly" : true }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"user" :"scheduler" , "namespace" : "*" , "resource" : "bindings" }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"user" :"kubelet" , "namespace" : "*" , "resource" : "pods" , "readonly" : true }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"user" :"kubelet" , "namespace" : "*" , "resource" : "services" , "readonly" : true }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"user" :"kubelet" , "namespace" : "*" , "resource" : "endpoints" , "readonly" : true }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"user" :"kubelet" , "namespace" : "*" , "resource" : "events" }}
{"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" , "kind" : "Policy" , "spec" : {"user" :"alice" , "namespace" : "projectCaribou" , "resource" : "*" , "apiGroup" : "*" }}
ABAC授权器定义在/pkg/auth/authorizer/abac/abac.go中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
type policyList []*api.Policy
func NewFromFile (path string ) (policyList, error) {
file, err := os.Open(path)
if err != nil {
return nil , err
}
defer file.Close()
scanner := bufio.NewScanner(file)
pl := make (policyList, 0 )
decoder := api.Codecs.UniversalDecoder()
i := 0
unversionedLines := 0
for scanner.Scan() {
i++
p := &api.Policy{}
b := scanner.Bytes()
trimmed := strings.TrimSpace(string (b))
if len (trimmed) == 0 || strings.HasPrefix(trimmed, "#" ) {
continue
}
decodedObj, _, err := decoder.Decode(b, nil , nil )
if err != nil {
if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
return nil , policyLoadError{path, i, b, err}
}
unversionedLines++
oldPolicy := &v0.Policy{}
if err := runtime.DecodeInto(decoder, b, oldPolicy); err != nil {
return nil , policyLoadError{path, i, b, err}
}
if err := api.Scheme.Convert(oldPolicy, p, nil ); err != nil {
return nil , policyLoadError{path, i, b, err}
}
pl = append (pl, p)
continue
}
decodedPolicy, ok := decodedObj.(*api.Policy)
if !ok {
return nil , policyLoadError{path, i, b, fmt.Errorf("unrecognized object: %#v" , decodedObj)}
}
pl = append (pl, decodedPolicy)
}
if unversionedLines > 0 {
glog.Warningf("Policy file %s contained unversioned rules. See docs/admin/authorization.md#abac-mode for ABAC file format details." , path)
}
if err := scanner.Err(); err != nil {
return nil , policyLoadError{path, -1 , nil , err}
}
return pl, nil
}
NewFromFile()读取ABAC policy文件,然后构建policyList,并返回。 policyList类型实现了Authorize()方法:1
2
3
4
5
6
7
8
9
10
11
12
func (pl policyList) Authorize (a authorizer.Attributes) (bool , string , error) {
for _, p := range pl {
if matches(*p, a) {
return true , "" , nil
}
}
return false , "No policy matched." , nil
}
Authorize()方法会使用policyList中每一条policy去匹配请求。如果匹配到,则直接通过;否则不通过。
再来看下匹配函数matches():1
2
3
4
5
6
7
8
9
10
11
12
13
14
func matches (p api.Policy, a authorizer.Attributes) bool {
if subjectMatches(p, a) {
if verbMatches(p, a) {
if resourceMatches(p, a) {
return true
}
if nonResourceMatches(p, a) {
return true
}
}
}
return false
}
matches()会执行subjectMatches(),verbMatches()及resourceMatches()或nonResourceMatches(),这些函数实现不做具体分析。
RBAC授权器 RBAC是指基于角色的访问控制。可以把用户和角色相关联,从而获取用户拥有的权限。首先来看下角色,在Kubernetes中,有两种类型的角色,一种是namespaced的角色;另一种是全局的角色。 namespaced的角色Yaml文件如下:1
2
3
4
5
6
7
8
9
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch" , "list" ]
全局的角色Yaml文件如下:1
2
3
4
5
6
7
8
9
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch" , "list" ]
一个角色对应着多条的规则。
当然,还需要一种机制关联角色和用户。这就是RoleBinding和ClusterRoleBinding。例子(表示把User jane绑定到Role pod-reader上)如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
具体RBAC的功能可以参照官方文档。
RBAC授权器定义在/plugin/pkg/auth/authorizer/rbac/rbac.go中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type RBACAuthorizer struct {
superUser string
authorizationRuleResolver RequestToRuleMapper
}
func New (roles validation.RoleGetter, roleBindings validation.RoleBindingLister, clusterRoles validation.ClusterRoleGetter, clusterRoleBindings validation.ClusterRoleBindingLister, superUser string ) *RBACAuthorizer {
authorizer := &RBACAuthorizer{
superUser: superUser,
authorizationRuleResolver: validation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}
return authorizer
}
RBAC授权方法如下:1
2
3
4
5
6
7
8
9
10
11
12
func (r *RBACAuthorizer) Authorize (requestAttributes authorizer.Attributes) (bool , string , error) {
if r.superUser != "" && requestAttributes.GetUser() != nil && requestAttributes.GetUser().GetName() == r.superUser {
return true , "" , nil
}
rules, ruleResolutionError := r.authorizationRuleResolver.RulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace())
if RulesAllow(requestAttributes, rules...) {
return true , "" , nil
}
return false , "" , ruleResolutionError
}
可以看出,如果是使用superUser来访问,则授权直接通过;否则使用RulesFor()获取用户的规则,再调用RulesAllow()来授权。 RulesFor()不作详细分析,通过上面提到的Bindings来获用户取对应的角色及角色对应的规则。 RulesAllow()定义如下:1
2
3
4
5
6
7
8
9
func RulesAllow (requestAttributes authorizer.Attributes, rules ...rbac.PolicyRule) bool {
for _, rule := range rules {
if RuleAllows(requestAttributes, rule) {
return true
}
}
return false
}
RulesAllow()会使用每一个规则去匹配请求。 RuleAllow()定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func RuleAllows (requestAttributes authorizer.Attributes, rule rbac.PolicyRule) bool {
if requestAttributes.IsResourceRequest() {
resource := requestAttributes.GetResource()
if len (requestAttributes.GetSubresource()) > 0 {
resource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource()
}
return rbac.VerbMatches(rule, requestAttributes.GetVerb()) &&
rbac.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
rbac.ResourceMatches(rule, resource) &&
rbac.ResourceNameMatches(rule, requestAttributes.GetName())
}
return rbac.VerbMatches(rule, requestAttributes.GetVerb()) &&
rbac.NonResourceURLMatches(rule, requestAttributes.GetPath())
}
RuleAllows()通过调用VerbMatches(), APIGroupMatches(), ResourceMatches(), ResourceNameMatches()等函数进行规则匹配,这些就不再展开分析。
对请求进行授权 那么,我们对Apiserver的请求是什么时候进行授权的呢? 和认证器一样,先来看/pkg/genericapiserver/config.go:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func DefaultBuildHandlerChain (apiHandler http.Handler, c *Config) (secure, insecure http.Handler) {
attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper)
generic := func (handler http.Handler) http .Handler {
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil , nil , nil , "true" )
handler = genericfilters.WithPanicRecovery(handler, c.RequestContextMapper)
handler = apiserverfilters.WithRequestInfo(handler, NewRequestInfoResolver(c), c.RequestContextMapper)
handler = api.WithRequestContext(handler, c.RequestContextMapper)
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc)
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.LongRunningFunc)
return handler
}
audit := func (handler http.Handler) http .Handler {
return apiserverfilters.WithAudit(handler, attributeGetter, c.AuditWriter)
}
protect := func (handler http.Handler) http .Handler {
handler = apiserverfilters.WithAuthorization(handler, attributeGetter, c.Authorizer)
handler = apiserverfilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer)
handler = audit(handler)
handler = authhandlers.WithAuthentication(handler, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth))
return handler
}
return generic(protect(apiHandler)), generic(audit(apiHandler))
}
再来看下/pkg/apiserver/filters/authorization.go中的WithAuthorization():1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func WithAuthorization (handler http.Handler, getAttribs RequestAttributeGetter, a authorizer.Authorizer) http .Handler {
if a == nil {
glog.Warningf("Authorization is disabled" )
return handler
}
return http.HandlerFunc(func (w http.ResponseWriter, req *http.Request) {
attrs, err := getAttribs.GetAttribs(req)
if err != nil {
internalError(w, req, err)
return
}
authorized, reason, err := a.Authorize(attrs)
if authorized {
handler.ServeHTTP(w, req)
return
}
if err != nil {
internalError(w, req, err)
return
}
glog.V(4 ).Infof("Forbidden: %#v, Reason: %s" , req.RequestURI, reason)
forbidden(w, req)
})
}
接下来的过程和认证器一样,这里就不再赘述。