什么是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
//***安装authorizer plugin***//
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)
}
// Keep cases in sync with constant list above.
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
// unionAuthzHandler authorizer against a chain of authorizer.Authorizer
type unionAuthzHandler []authorizer.Authorizer
// New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
return unionAuthzHandler(authorizationHandlers)
}
// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful
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
//***Policy定义在/pkg/apis/abac/types.go中***//
type policyList []*api.Policy
//***从policy文件构建plicylist***//
func NewFromFile(path string) (policyList, error) {
// File format is one map per line. This allows easy concatentation of files,
// comments in files, and identification of errors by line number.
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
//***Fankang***//
//***新建scanner,固定用法***//
scanner := bufio.NewScanner(file)
pl := make(policyList, 0)
decoder := api.Codecs.UniversalDecoder()
i := 0
unversionedLines := 0
//***Fankang***//
//***按行读取***//
for scanner.Scan() {
i++
p := &api.Policy{}
//***Fankang***//
//***读取行***//
b := scanner.Bytes()
// skip comment lines and blank lines
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++
// Migrate unversioned policy object
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
// Authorizer implements authorizer.Authorize
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
// TODO: Benchmark how much time policy matching takes with a medium size
// policy file, compared to other steps such as encoding/decoding.
// Then, add Caching only if needed.
}

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) {
// Resource and non-resource requests are mutually exclusive, at most one will match a policy
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: [""] # "" indicates the core API group
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:
# "namespace" omitted since ClusterRoles are not namespaced
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
# This role binding allows "jane" to read pods in the "default" namespace.
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
//***通过调用WithAuthentication(), WithAuthorization()等构建handler处理链***//
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 {
//***封装在/pkg/apiserver/filters/authorization.go中***//
handler = apiserverfilters.WithAuthorization(handler, attributeGetter, c.Authorizer)
handler = apiserverfilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer)
handler = audit(handler) // before impersonation to read original user
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
//***Authorization封装***//
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
}
//***执行Authorize()操作***//
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)
})
}

接下来的过程和认证器一样,这里就不再赘述。