package kubernetes import ( "crypto/x509" "net/http" "strings" "time" "github.com/golang/glog" "k8s.io/kubernetes/pkg/auth/authenticator" kauthorizer "k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/client/restclient" oauthenticator "github.com/openshift/origin/pkg/auth/authenticator" "github.com/openshift/origin/pkg/auth/authenticator/anonymous" "github.com/openshift/origin/pkg/auth/authenticator/request/bearertoken" "github.com/openshift/origin/pkg/auth/authenticator/request/unionrequest" "github.com/openshift/origin/pkg/auth/authenticator/request/x509request" authncache "github.com/openshift/origin/pkg/auth/authenticator/token/cache" authnremote "github.com/openshift/origin/pkg/auth/authenticator/token/remotemaster" "github.com/openshift/origin/pkg/auth/group" authorizationapi "github.com/openshift/origin/pkg/authorization/api" oauthorizer "github.com/openshift/origin/pkg/authorization/authorizer" authzadapter "github.com/openshift/origin/pkg/authorization/authorizer/adapter" authzcache "github.com/openshift/origin/pkg/authorization/authorizer/cache" authzremote "github.com/openshift/origin/pkg/authorization/authorizer/remote" oclient "github.com/openshift/origin/pkg/client" "github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" ) func newAuthenticator(clientCAs *x509.CertPool, anonymousConfig restclient.Config, cacheTTL time.Duration, cacheSize int) (authenticator.Request, error) { authenticators := []oauthenticator.Request{} // API token auth var ( tokenAuthenticator oauthenticator.Token err error ) // Authenticate against the remote master tokenAuthenticator, err = authnremote.NewAuthenticator(anonymousConfig) if err != nil { return nil, err } // Cache results if cacheTTL > 0 && cacheSize > 0 { tokenAuthenticator, err = authncache.NewAuthenticator(tokenAuthenticator, cacheTTL, cacheSize) if err != nil { return nil, err } } authenticators = append(authenticators, bearertoken.New(tokenAuthenticator, true)) // Client-cert auth if clientCAs != nil { opts := x509request.DefaultVerifyOptions() opts.Roots = clientCAs certauth := x509request.New(opts, x509request.SubjectToUserConversion) authenticators = append(authenticators, certauth) } ret := &unionrequest.Authenticator{ // Anonymous requests will pass the token and cert checks without errors // Bad tokens or bad certs will produce errors, in which case we should not continue to authenticate them as "system:anonymous" FailOnError: true, Handlers: []oauthenticator.Request{ // Add the "system:authenticated" group to users that pass token/cert authentication group.NewGroupAdder(unionrequest.NewUnionAuthentication(authenticators...), []string{bootstrappolicy.AuthenticatedGroup}), // Fall back to the "system:anonymous" user anonymous.NewAuthenticator(), }, } return ret, nil } func newAuthorizerAttributesGetter(nodeName string) (kauthorizer.RequestAttributesGetter, error) { return NodeAuthorizerAttributesGetter{nodeName}, nil } type NodeAuthorizerAttributesGetter struct { nodeName string } func isSubpath(r *http.Request, path string) bool { path = strings.TrimSuffix(path, "/") return r.URL.Path == path || strings.HasPrefix(r.URL.Path, path+"/") } // GetRequestAttributes populates authorizer attributes for the requests to the kubelet API. // Default attributes are {apiVersion=v1,verb=proxy,resource=nodes,resourceName=} // More specific verb/resource is set for the following request patterns: // /stats/* => verb=, resource=nodes/stats // /metrics/* => verb=, resource=nodes/metrics // /logs/* => verb=, resource=nodes/log func (n NodeAuthorizerAttributesGetter) GetRequestAttributes(u user.Info, r *http.Request) kauthorizer.Attributes { // Default verb/resource is proxy nodes, which allows full access to the kubelet API attrs := oauthorizer.DefaultAuthorizationAttributes{ APIVersion: "v1", APIGroup: "", Verb: "proxy", Resource: "nodes", ResourceName: n.nodeName, URL: r.URL.Path, } namespace := "" userName := u.GetName() groups := u.GetGroups() apiVerb := "" switch r.Method { case "POST": apiVerb = "create" case "GET": apiVerb = "get" case "PUT": apiVerb = "update" case "PATCH": apiVerb = "patch" case "DELETE": apiVerb = "delete" } // Override verb/resource for specific paths // Updates to these rules require updating NodeAdminRole and NodeReaderRole in bootstrap policy switch { case isSubpath(r, "/stats"): attrs.Verb = apiVerb attrs.Resource = authorizationapi.NodeStatsResource case isSubpath(r, "/metrics"): attrs.Verb = apiVerb attrs.Resource = authorizationapi.NodeMetricsResource case isSubpath(r, "/logs"): attrs.Verb = apiVerb attrs.Resource = authorizationapi.NodeLogResource } // TODO: handle other things like /healthz/*? not sure if "non-resource" urls on the kubelet make sense to authorize against master non-resource URL policy glog.V(2).Infof("Node request attributes: namespace=%s, user=%s, groups=%v, attrs=%#v", namespace, userName, groups, attrs) return authzadapter.KubernetesAuthorizerAttributes(namespace, userName, groups, attrs) } func newAuthorizer(c *oclient.Client, cacheTTL time.Duration, cacheSize int) (kauthorizer.Authorizer, error) { var ( authz oauthorizer.Authorizer err error ) // Authorize against the remote master authz, err = authzremote.NewAuthorizer(c) if err != nil { return nil, err } // Cache results if cacheTTL > 0 && cacheSize > 0 { authz, err = authzcache.NewAuthorizer(authz, cacheTTL, cacheSize) if err != nil { return nil, err } } // Adapt to the Kubernetes authorizer interface kauthz, err := authzadapter.NewAuthorizer(authz) if err != nil { return nil, err } return kauthz, nil }