func TestCLIGetToken(t *testing.T) { testutil.RequireEtcd(t) _, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI() checkErr(t, err) clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) checkErr(t, err) anonymousConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig) reader := bytes.NewBufferString("user\npass") accessToken, err := tokencmd.RequestToken(&anonymousConfig, reader, "", "") if err != nil { t.Errorf("Unexpected error: %v", err) } if len(accessToken) == 0 { t.Error("Expected accessToken, but did not get one") } clientConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig) clientConfig.BearerToken = accessToken osClient, err := client.New(&clientConfig) checkErr(t, err) user, err := osClient.Users().Get("~") checkErr(t, err) if user.Name != "user" { t.Errorf("expected %v, got %v", "user", user.Name) } }
func GetScopedClientForUser(adminClient *client.Client, clientConfig restclient.Config, username string, scopes []string) (*client.Client, *kclient.Client, *restclient.Config, error) { // make sure the user exists if _, _, _, err := GetClientForUser(clientConfig, username); err != nil { return nil, nil, nil, err } user, err := adminClient.Users().Get(username) if err != nil { return nil, nil, nil, err } token := &oauthapi.OAuthAccessToken{ ObjectMeta: kapi.ObjectMeta{Name: fmt.Sprintf("%s-token-plus-some-padding-here-to-make-the-limit-%d", username, rand.Int())}, ClientName: origin.OpenShiftCLIClientID, ExpiresIn: 86400, Scopes: scopes, RedirectURI: "https://127.0.0.1:12000/oauth/token/implicit", UserName: user.Name, UserUID: string(user.UID), } if _, err := adminClient.OAuthAccessTokens().Create(token); err != nil { return nil, nil, nil, err } scopedConfig := clientcmd.AnonymousClientConfig(&clientConfig) scopedConfig.BearerToken = token.Name kubeClient, err := kclient.New(&scopedConfig) if err != nil { return nil, nil, nil, err } osClient, err := client.New(&scopedConfig) if err != nil { return nil, nil, nil, err } return osClient, kubeClient, &scopedConfig, nil }
// NewAuthenticator authenticates by fetching users/~ using the provided token as a bearer token func NewAuthenticator(anonymousConfig kclient.Config) (*Authenticator, error) { // Ensure credentials are removed from the anonymous config anonymousConfig = clientcmd.AnonymousClientConfig(anonymousConfig) return &Authenticator{ anonymousConfig: anonymousConfig, }, nil }
func GetClientForServiceAccount(adminClient *kclientset.Clientset, clientConfig restclient.Config, namespace, name string) (*client.Client, *kclientset.Clientset, *restclient.Config, error) { _, err := adminClient.Core().Namespaces().Create(&kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: namespace}}) if err != nil && !kerrs.IsAlreadyExists(err) { return nil, nil, nil, err } sa, err := adminClient.Core().ServiceAccounts(namespace).Create(&kapi.ServiceAccount{ObjectMeta: kapi.ObjectMeta{Name: name}}) if kerrs.IsAlreadyExists(err) { sa, err = adminClient.Core().ServiceAccounts(namespace).Get(name) } if err != nil { return nil, nil, nil, err } token := "" err = wait.Poll(time.Second, 30*time.Second, func() (bool, error) { selector := fields.OneTermEqualSelector(kapi.SecretTypeField, string(kapi.SecretTypeServiceAccountToken)) secrets, err := adminClient.Core().Secrets(namespace).List(kapi.ListOptions{FieldSelector: selector}) if err != nil { return false, err } for _, secret := range secrets.Items { if serviceaccounts.IsValidServiceAccountToken(sa, &secret) { token = string(secret.Data[kapi.ServiceAccountTokenKey]) return true, nil } } return false, nil }) if err != nil { return nil, nil, nil, err } saClientConfig := clientcmd.AnonymousClientConfig(&clientConfig) saClientConfig.BearerToken = token kubeClient, err := kclient.New(&saClientConfig) if err != nil { return nil, nil, nil, err } kubeClientset := adapter.FromUnversionedClient(kubeClient) osClient, err := client.New(&saClientConfig) if err != nil { return nil, nil, nil, err } return osClient, kubeClientset, &saClientConfig, nil }
func GetClientForUser(clientConfig kclient.Config, username string) (*client.Client, *kclient.Client, *kclient.Config, error) { token, err := tokencmd.RequestToken(&clientConfig, nil, username, "password") if err != nil { return nil, nil, nil, err } userClientConfig := clientcmd.AnonymousClientConfig(clientConfig) userClientConfig.BearerToken = token kubeClient, err := kclient.New(&userClientConfig) if err != nil { return nil, nil, nil, err } osClient, err := client.New(&userClientConfig) if err != nil { return nil, nil, nil, err } return osClient, kubeClient, &userClientConfig, nil }
// TODO internalclientset: get rid of oldClient after next rebase func GetClientForUser(clientConfig restclient.Config, username string) (*client.Client, *kclientset.Clientset, *restclient.Config, error) { token, err := tokencmd.RequestToken(&clientConfig, nil, username, "password") if err != nil { return nil, nil, nil, err } userClientConfig := clientcmd.AnonymousClientConfig(&clientConfig) userClientConfig.BearerToken = token kubeClient, err := kclient.New(&userClientConfig) if err != nil { return nil, nil, nil, err } kubeClientset := adapter.FromUnversionedClient(kubeClient) osClient, err := client.New(&userClientConfig) if err != nil { return nil, nil, nil, err } return osClient, kubeClientset, &userClientConfig, nil }
func TestNodeAuth(t *testing.T) { // Server config masterConfig, nodeConfig, adminKubeConfigFile, err := testserver.StartTestAllInOne() if err != nil { t.Fatalf("unexpected error: %v", err) } // Cluster admin clients and client configs adminClient, err := testutil.GetClusterAdminKubeClient(adminKubeConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } originAdminClient, err := testutil.GetClusterAdminClient(adminKubeConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } adminConfig, err := testutil.GetClusterAdminClientConfig(adminKubeConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } // Client configs for lesser users masterKubeletClientConfig := configapi.GetKubeletClientConfig(*masterConfig) anonymousConfig := clientcmd.AnonymousClientConfig(*adminConfig) badTokenConfig := clientcmd.AnonymousClientConfig(*adminConfig) badTokenConfig.BearerToken = "bad-token" bobClient, _, bobConfig, err := testutil.GetClientForUser(*adminConfig, "bob") _, _, aliceConfig, err := testutil.GetClientForUser(*adminConfig, "alice") sa1Client, _, sa1Config, err := testutil.GetClientForServiceAccount(adminClient, *adminConfig, "default", "sa1") _, _, sa2Config, err := testutil.GetClientForServiceAccount(adminClient, *adminConfig, "default", "sa2") // Grant Bob system:node-reader, which should let them read metrics and stats addBob := &policy.RoleModificationOptions{ RoleName: bootstrappolicy.NodeReaderRoleName, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(originAdminClient), Subjects: []kapi.ObjectReference{{Kind: "User", Name: "bob"}}, } if err := addBob.AddRole(); err != nil { t.Fatalf("unexpected error: %v", err) } // Grant sa1 system:cluster-reader, which should let them read metrics and stats addSA1 := &policy.RoleModificationOptions{ RoleName: bootstrappolicy.ClusterReaderRoleName, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(originAdminClient), Subjects: []kapi.ObjectReference{{Kind: "ServiceAccount", Namespace: "default", Name: "sa1"}}, } if err := addSA1.AddRole(); err != nil { t.Fatalf("unexpected error: %v", err) } // Wait for policy cache if err := testutil.WaitForClusterPolicyUpdate(bobClient, "get", "nodes/metrics", true); err != nil { t.Fatalf("unexpected error: %v", err) } if err := testutil.WaitForClusterPolicyUpdate(sa1Client, "get", "nodes/metrics", true); err != nil { t.Fatalf("unexpected error: %v", err) } _, nodePort, err := net.SplitHostPort(nodeConfig.ServingInfo.BindAddress) if err != nil { t.Fatalf("unexpected error: %v", err) } nodePortInt, err := strconv.ParseInt(nodePort, 0, 0) if err != nil { t.Fatalf("unexpected error: %v", err) } nodeTLS := configapi.UseTLS(nodeConfig.ServingInfo) kubeletClientConfig := func(config *kclient.Config) *kubeletclient.KubeletClientConfig { return &kubeletclient.KubeletClientConfig{ Port: uint(nodePortInt), EnableHttps: nodeTLS, TLSClientConfig: config.TLSClientConfig, BearerToken: config.BearerToken, } } testCases := map[string]struct { KubeletClientConfig *kubeletclient.KubeletClientConfig Forbidden bool NodeViewer bool NodeAdmin bool }{ "bad token": { KubeletClientConfig: kubeletClientConfig(&badTokenConfig), }, "anonymous": { KubeletClientConfig: kubeletClientConfig(&anonymousConfig), Forbidden: true, }, "cluster admin": { KubeletClientConfig: kubeletClientConfig(adminConfig), NodeAdmin: true, }, "master kubelet client": { KubeletClientConfig: masterKubeletClientConfig, NodeAdmin: true, }, "bob": { KubeletClientConfig: kubeletClientConfig(bobConfig), NodeViewer: true, }, "alice": { KubeletClientConfig: kubeletClientConfig(aliceConfig), Forbidden: true, }, "sa1": { KubeletClientConfig: kubeletClientConfig(sa1Config), NodeViewer: true, }, "sa2": { KubeletClientConfig: kubeletClientConfig(sa2Config), Forbidden: true, }, } for k, tc := range testCases { var ( // expected result for requests a viewer should be able to make viewResult int // expected result for requests an admin should be able to make (that can actually complete with a 200 in our tests) adminResultOK int // expected result for requests an admin should be able to make (that return a 404 in this test if the authn/authz layer is completed) adminResultMissing int ) switch { case tc.NodeAdmin: viewResult = http.StatusOK adminResultOK = http.StatusOK adminResultMissing = http.StatusNotFound case tc.NodeViewer: viewResult = http.StatusOK adminResultOK = http.StatusForbidden adminResultMissing = http.StatusForbidden case tc.Forbidden: viewResult = http.StatusForbidden adminResultOK = http.StatusForbidden adminResultMissing = http.StatusForbidden default: viewResult = http.StatusUnauthorized adminResultOK = http.StatusUnauthorized adminResultMissing = http.StatusUnauthorized } requests := []testRequest{ // Responses to invalid paths are the same for all users {"GET", "/", http.StatusNotFound}, {"GET", "/stats", http.StatusMovedPermanently}, // ServeMux redirects to the directory {"GET", "/logs", http.StatusMovedPermanently}, // ServeMux redirects to the directory {"GET", "/invalid", http.StatusNotFound}, // viewer requests {"GET", "/metrics", viewResult}, {"GET", "/stats/", viewResult}, {"POST", "/stats/", viewResult}, // stats requests can be POSTs which contain query options // successful admin requests {"GET", "/healthz", adminResultOK}, {"GET", "/pods", adminResultOK}, {"GET", "/logs/", adminResultOK}, // not found admin requests {"GET", "/containerLogs/mynamespace/mypod/mycontainer", adminResultMissing}, {"POST", "/exec/mynamespace/mypod/mycontainer", adminResultMissing}, {"POST", "/run/mynamespace/mypod/mycontainer", adminResultMissing}, {"POST", "/attach/mynamespace/mypod/mycontainer", adminResultMissing}, {"POST", "/portForward/mynamespace/mypod/mycontainer", adminResultMissing}, // GET is supported in origin on /exec and /attach for backwards compatibility // make sure node admin permissions are required {"GET", "/exec/mynamespace/mypod/mycontainer", adminResultMissing}, {"GET", "/attach/mynamespace/mypod/mycontainer", adminResultMissing}, } rt, err := kubeletclient.MakeTransport(tc.KubeletClientConfig) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) continue } for _, r := range requests { req, err := http.NewRequest(r.Method, "https://"+nodeConfig.NodeName+":10250"+r.Path, nil) if err != nil { t.Errorf("%s: %s: unexpected error: %v", k, r.Path, err) continue } resp, err := rt.RoundTrip(req) if err != nil { t.Errorf("%s: %s: unexpected error: %v", k, r.Path, err) continue } resp.Body.Close() if resp.StatusCode != r.Result { t.Errorf("%s: %s: expected %d, got %d", k, r.Path, r.Result, resp.StatusCode) continue } } } }
func TestAuthorizationSubjectAccessReview(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "hammer-project", "harold") if err != nil { t.Fatalf("unexpected error: %v", err) } markClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, "mallet-project", "mark") if err != nil { t.Fatalf("unexpected error: %v", err) } dannyClient, _, dannyConfig, err := testutil.GetClientForUser(*clusterAdminClientConfig, "danny") if err != nil { t.Fatalf("error requesting token: %v", err) } anonymousConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig) anonymousClient, err := client.New(&anonymousConfig) if err != nil { t.Fatalf("error getting anonymous client: %v", err) } addAnonymous := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: bootstrappolicy.EditRoleName, RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("hammer-project", clusterAdminClient), Users: []string{"system:anonymous"}, } if err := addAnonymous.AddRole(); err != nil { t.Errorf("unexpected error: %v", err) } addDanny := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: bootstrappolicy.ViewRoleName, RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("default", clusterAdminClient), Users: []string{"danny"}, } if err := addDanny.AddRole(); err != nil { t.Errorf("unexpected error: %v", err) } askCanDannyGetProject := &authorizationapi.SubjectAccessReview{ User: "******", Action: authorizationapi.Action{Verb: "get", Resource: "projects"}, } subjectAccessReviewTest{ description: "cluster admin told danny can get project default", localInterface: clusterAdminClient.LocalSubjectAccessReviews("default"), localReview: &authorizationapi.LocalSubjectAccessReview{ User: "******", Action: authorizationapi.Action{Verb: "get", Resource: "projects"}, }, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: true, Reason: "allowed by rule in default", Namespace: "default", }, }.run(t) subjectAccessReviewTest{ description: "cluster admin told danny cannot get projects cluster-wide", clusterInterface: clusterAdminClient.SubjectAccessReviews(), clusterReview: askCanDannyGetProject, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "danny" cannot get projects at the cluster scope`, Namespace: "", }, }.run(t) subjectAccessReviewTest{ description: "as danny, can I make cluster subject access reviews", clusterInterface: dannyClient.SubjectAccessReviews(), clusterReview: askCanDannyGetProject, err: `User "danny" cannot create subjectaccessreviews at the cluster scope`, }.run(t) subjectAccessReviewTest{ description: "as anonymous, can I make cluster subject access reviews", clusterInterface: anonymousClient.SubjectAccessReviews(), clusterReview: askCanDannyGetProject, err: `User "system:anonymous" cannot create subjectaccessreviews at the cluster scope`, }.run(t) addValerie := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: bootstrappolicy.ViewRoleName, RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("hammer-project", haroldClient), Users: []string{"valerie"}, } if err := addValerie.AddRole(); err != nil { t.Errorf("unexpected error: %v", err) } addEdgar := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: bootstrappolicy.EditRoleName, RoleBindingAccessor: policy.NewLocalRoleBindingAccessor("mallet-project", markClient), Users: []string{"edgar"}, } if err := addEdgar.AddRole(); err != nil { t.Fatalf("unexpected error: %v", err) } askCanValerieGetProject := &authorizationapi.LocalSubjectAccessReview{ User: "******", Action: authorizationapi.Action{Verb: "get", Resource: "projects"}, } subjectAccessReviewTest{ description: "harold told valerie can get project hammer-project", localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"), localReview: askCanValerieGetProject, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: true, Reason: "allowed by rule in hammer-project", Namespace: "hammer-project", }, }.run(t) subjectAccessReviewTest{ description: "mark told valerie cannot get project mallet-project", localInterface: markClient.LocalSubjectAccessReviews("mallet-project"), localReview: askCanValerieGetProject, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "valerie" cannot get projects in project "mallet-project"`, Namespace: "mallet-project", }, }.run(t) askCanEdgarDeletePods := &authorizationapi.LocalSubjectAccessReview{ User: "******", Action: authorizationapi.Action{Verb: "delete", Resource: "pods"}, } subjectAccessReviewTest{ description: "mark told edgar can delete pods in mallet-project", localInterface: markClient.LocalSubjectAccessReviews("mallet-project"), localReview: askCanEdgarDeletePods, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: true, Reason: "allowed by rule in mallet-project", Namespace: "mallet-project", }, }.run(t) // ensure unprivileged users cannot check other users' access subjectAccessReviewTest{ description: "harold denied ability to run subject access review in project mallet-project", localInterface: haroldClient.LocalSubjectAccessReviews("mallet-project"), localReview: askCanEdgarDeletePods, err: `User "harold" cannot create localsubjectaccessreviews in project "mallet-project"`, }.run(t) subjectAccessReviewTest{ description: "system:anonymous denied ability to run subject access review in project mallet-project", localInterface: anonymousClient.LocalSubjectAccessReviews("mallet-project"), localReview: askCanEdgarDeletePods, err: `User "system:anonymous" cannot create localsubjectaccessreviews in project "mallet-project"`, }.run(t) // ensure message does not leak whether the namespace exists or not subjectAccessReviewTest{ description: "harold denied ability to run subject access review in project nonexistent-project", localInterface: haroldClient.LocalSubjectAccessReviews("nonexistent-project"), localReview: askCanEdgarDeletePods, err: `User "harold" cannot create localsubjectaccessreviews in project "nonexistent-project"`, }.run(t) subjectAccessReviewTest{ description: "system:anonymous denied ability to run subject access review in project nonexistent-project", localInterface: anonymousClient.LocalSubjectAccessReviews("nonexistent-project"), localReview: askCanEdgarDeletePods, err: `User "system:anonymous" cannot create localsubjectaccessreviews in project "nonexistent-project"`, }.run(t) askCanHaroldUpdateProject := &authorizationapi.LocalSubjectAccessReview{ User: "******", Action: authorizationapi.Action{Verb: "update", Resource: "projects"}, } subjectAccessReviewTest{ description: "harold told harold can update project hammer-project", localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"), localReview: askCanHaroldUpdateProject, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: true, Reason: "allowed by rule in hammer-project", Namespace: "hammer-project", }, }.run(t) askCanClusterAdminsCreateProject := &authorizationapi.SubjectAccessReview{ Groups: sets.NewString("system:cluster-admins"), Action: authorizationapi.Action{Verb: "create", Resource: "projects"}, } subjectAccessReviewTest{ description: "cluster admin told cluster admins can create projects", clusterInterface: clusterAdminClient.SubjectAccessReviews(), clusterReview: askCanClusterAdminsCreateProject, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: true, Reason: "allowed by cluster rule", Namespace: "", }, }.run(t) subjectAccessReviewTest{ description: "harold denied ability to run cluster subject access review", clusterInterface: haroldClient.SubjectAccessReviews(), clusterReview: askCanClusterAdminsCreateProject, err: `User "harold" cannot create subjectaccessreviews at the cluster scope`, }.run(t) askCanICreatePods := &authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.Action{Verb: "create", Resource: "pods"}, } subjectAccessReviewTest{ description: "harold told he can create pods in project hammer-project", localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"), localReview: askCanICreatePods, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: true, Reason: "allowed by rule in hammer-project", Namespace: "hammer-project", }, }.run(t) subjectAccessReviewTest{ description: "system:anonymous told he can create pods in project hammer-project", localInterface: anonymousClient.LocalSubjectAccessReviews("hammer-project"), localReview: askCanICreatePods, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: true, Reason: "allowed by rule in hammer-project", Namespace: "hammer-project", }, }.run(t) // test checking self permissions when denied subjectAccessReviewTest{ description: "harold told he cannot create pods in project mallet-project", localInterface: haroldClient.LocalSubjectAccessReviews("mallet-project"), localReview: askCanICreatePods, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "harold" cannot create pods in project "mallet-project"`, Namespace: "mallet-project", }, }.run(t) subjectAccessReviewTest{ description: "system:anonymous told he cannot create pods in project mallet-project", localInterface: anonymousClient.LocalSubjectAccessReviews("mallet-project"), localReview: askCanICreatePods, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "system:anonymous" cannot create pods in project "mallet-project"`, Namespace: "mallet-project", }, }.run(t) // test checking self-permissions doesn't leak whether namespace exists or not subjectAccessReviewTest{ description: "harold told he cannot create pods in project nonexistent-project", localInterface: haroldClient.LocalSubjectAccessReviews("nonexistent-project"), localReview: askCanICreatePods, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "harold" cannot create pods in project "nonexistent-project"`, Namespace: "nonexistent-project", }, }.run(t) subjectAccessReviewTest{ description: "system:anonymous told he cannot create pods in project nonexistent-project", localInterface: anonymousClient.LocalSubjectAccessReviews("nonexistent-project"), localReview: askCanICreatePods, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "system:anonymous" cannot create pods in project "nonexistent-project"`, Namespace: "nonexistent-project", }, }.run(t) askCanICreatePolicyBindings := &authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.Action{Verb: "create", Resource: "policybindings"}, } subjectAccessReviewTest{ description: "harold told he can create policybindings in project hammer-project", localInterface: haroldClient.LocalSubjectAccessReviews("hammer-project"), localReview: askCanICreatePolicyBindings, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "harold" cannot create policybindings in project "hammer-project"`, Namespace: "hammer-project", }, }.run(t) // impersonate SAR tests // impersonated empty token SAR shouldn't be allowed at all // impersonated danny token SAR shouldn't be allowed to see pods in hammer or in cluster // impersonated danny token SAR should be allowed to see pods in default // we need a token client for overriding if err != nil { t.Fatalf("unexpected error: %v", err) } otherAdminClient, _, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, "other-admin") if err != nil { t.Fatalf("error requesting token: %v", err) } addOtherAdmin := &policy.RoleModificationOptions{ RoleNamespace: "", RoleName: bootstrappolicy.ClusterAdminRoleName, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(clusterAdminClient), Users: []string{"other-admin"}, } if err := addOtherAdmin.AddRole(); err != nil { t.Errorf("unexpected error: %v", err) } subjectAccessReviewTest{ description: "empty token impersonate can't see pods in namespace", localInterface: otherAdminClient.ImpersonateLocalSubjectAccessReviews("hammer-project", ""), localReview: &authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.Action{Verb: "list", Resource: "pods"}, }, err: `impersonating token may not be empty`, }.run(t) subjectAccessReviewTest{ description: "empty token impersonate can't see pods in cluster", clusterInterface: otherAdminClient.ImpersonateSubjectAccessReviews(""), clusterReview: &authorizationapi.SubjectAccessReview{ Action: authorizationapi.Action{Verb: "list", Resource: "pods"}, }, err: `impersonating token may not be empty`, }.run(t) subjectAccessReviewTest{ description: "danny impersonate can't see pods in hammer namespace", localInterface: otherAdminClient.ImpersonateLocalSubjectAccessReviews("hammer-project", dannyConfig.BearerToken), localReview: &authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.Action{Verb: "list", Resource: "pods"}, }, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "danny" cannot list pods in project "hammer-project"`, Namespace: "hammer-project", }, }.run(t) subjectAccessReviewTest{ description: "danny impersonate can't see pods in cluster", clusterInterface: otherAdminClient.ImpersonateSubjectAccessReviews(dannyConfig.BearerToken), clusterReview: &authorizationapi.SubjectAccessReview{ Action: authorizationapi.Action{Verb: "list", Resource: "pods"}, }, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: false, Reason: `User "danny" cannot list all pods in the cluster`, }, }.run(t) subjectAccessReviewTest{ description: "danny impersonate can see pods in default", localInterface: otherAdminClient.ImpersonateLocalSubjectAccessReviews("default", dannyConfig.BearerToken), localReview: &authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.Action{Verb: "list", Resource: "pods"}, }, response: authorizationapi.SubjectAccessReviewResponse{ Allowed: true, Reason: `allowed by rule in default`, Namespace: "default", }, }.run(t) }
func TestScopedTokens(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } projectName := "hammer-project" userName := "******" haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projectName, userName) if err != nil { t.Fatalf("unexpected error: %v", err) } if _, err := haroldClient.Builds(projectName).List(kapi.ListOptions{}); err != nil { t.Fatalf("unexpected error: %v", err) } haroldUser, err := haroldClient.Users().Get("~") if err != nil { t.Fatalf("unexpected error: %v", err) } whoamiOnlyToken := &oauthapi.OAuthAccessToken{ ObjectMeta: kapi.ObjectMeta{Name: "whoami-token-plus-some-padding-here-to-make-the-limit"}, ClientName: origin.OpenShiftCLIClientID, ExpiresIn: 200, Scopes: []string{scope.UserInfo}, UserName: userName, UserUID: string(haroldUser.UID), } if _, err := clusterAdminClient.OAuthAccessTokens().Create(whoamiOnlyToken); err != nil { t.Fatalf("unexpected error: %v", err) } whoamiConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig) whoamiConfig.BearerToken = whoamiOnlyToken.Name whoamiClient, err := client.New(&whoamiConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } if _, err := whoamiClient.Builds(projectName).List(kapi.ListOptions{}); !kapierrors.IsForbidden(err) { t.Fatalf("unexpected error: %v", err) } user, err := whoamiClient.Users().Get("~") if err != nil { t.Fatalf("unexpected error: %v", err) } if user.Name != userName { t.Fatalf("expected %v, got %v", userName, user.Name) } // try to impersonate a service account using this token whoamiConfig.Impersonate = serviceaccount.MakeUsername(projectName, "default") impersonatingClient, err := client.New(&whoamiConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } impersonatedUser, err := impersonatingClient.Users().Get("~") if !kapierrors.IsForbidden(err) { t.Fatalf("missing error: %v got user %#v", err, impersonatedUser) } }
func TestScopeEscalations(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } projectName := "hammer-project" userName := "******" haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projectName, userName) if err != nil { t.Fatalf("unexpected error: %v", err) } if _, err := haroldClient.Builds(projectName).List(kapi.ListOptions{}); err != nil { t.Fatalf("unexpected error: %v", err) } haroldUser, err := haroldClient.Users().Get("~") if err != nil { t.Fatalf("unexpected error: %v", err) } nonEscalatingEditToken := &oauthapi.OAuthAccessToken{ ObjectMeta: kapi.ObjectMeta{Name: "non-escalating-edit-plus-some-padding-here-to-make-the-limit"}, ClientName: origin.OpenShiftCLIClientID, ExpiresIn: 200, Scopes: []string{scope.ClusterRoleIndicator + "edit:*"}, UserName: userName, UserUID: string(haroldUser.UID), } if _, err := clusterAdminClient.OAuthAccessTokens().Create(nonEscalatingEditToken); err != nil { t.Fatalf("unexpected error: %v", err) } nonEscalatingEditConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig) nonEscalatingEditConfig.BearerToken = nonEscalatingEditToken.Name nonEscalatingEditClient, err := kclientset.NewForConfig(&nonEscalatingEditConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } if _, err := nonEscalatingEditClient.Secrets(projectName).List(kapi.ListOptions{}); !kapierrors.IsForbidden(err) { t.Fatalf("unexpected error: %v", err) } escalatingEditToken := &oauthapi.OAuthAccessToken{ ObjectMeta: kapi.ObjectMeta{Name: "escalating-edit-plus-some-padding-here-to-make-the-limit"}, ClientName: origin.OpenShiftCLIClientID, ExpiresIn: 200, Scopes: []string{scope.ClusterRoleIndicator + "edit:*:!"}, UserName: userName, UserUID: string(haroldUser.UID), } if _, err := clusterAdminClient.OAuthAccessTokens().Create(escalatingEditToken); err != nil { t.Fatalf("unexpected error: %v", err) } escalatingEditConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig) escalatingEditConfig.BearerToken = escalatingEditToken.Name escalatingEditClient, err := kclientset.NewForConfig(&escalatingEditConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } if _, err := escalatingEditClient.Secrets(projectName).List(kapi.ListOptions{}); err != nil { t.Fatalf("unexpected error: %v", err) } }
// SafeClientConfig returns a client config without authentication info. func (r *registryClient) SafeClientConfig() restclient.Config { return clientcmd.AnonymousClientConfig(r.config.OpenShiftConfig()) }
func BuildKubernetesNodeConfig(options configapi.NodeConfig) (*NodeConfig, error) { kubeClient, _, err := configapi.GetKubeClient(options.MasterKubeConfig) if err != nil { return nil, err } if options.NodeName == "localhost" { glog.Warningf(`Using "localhost" as node name will not resolve from all locations`) } var dnsIP net.IP if len(options.DNSIP) > 0 { dnsIP = net.ParseIP(options.DNSIP) if dnsIP == nil { return nil, fmt.Errorf("Invalid DNS IP: %s", options.DNSIP) } } clientCAs, err := util.CertPoolFromFile(options.ServingInfo.ClientCA) if err != nil { return nil, err } imageTemplate := variable.NewDefaultImageTemplate() imageTemplate.Format = options.ImageConfig.Format imageTemplate.Latest = options.ImageConfig.Latest var path string var fileCheckInterval int64 if options.PodManifestConfig != nil { path = options.PodManifestConfig.Path fileCheckInterval = options.PodManifestConfig.FileCheckIntervalSeconds } var dockerExecHandler dockertools.ExecHandler switch options.DockerConfig.ExecHandlerName { case configapi.DockerExecHandlerNative: dockerExecHandler = &dockertools.NativeExecHandler{} case configapi.DockerExecHandlerNsenter: dockerExecHandler = &dockertools.NsenterExecHandler{} } kubeAddressStr, kubePortStr, err := net.SplitHostPort(options.ServingInfo.BindAddress) if err != nil { return nil, fmt.Errorf("cannot parse node address: %v", err) } kubePort, err := strconv.Atoi(kubePortStr) if err != nil { return nil, fmt.Errorf("cannot parse node port: %v", err) } kubeAddress := net.ParseIP(kubeAddressStr) if kubeAddress == nil { return nil, fmt.Errorf("Invalid DNS IP: %s", kubeAddressStr) } // declare the OpenShift defaults from config server := kapp.NewKubeletServer() server.Config = path server.RootDirectory = options.VolumeDirectory // kubelet finds the node IP address by doing net.ParseIP(hostname) and if that fails, // it does net.LookupIP(NodeName) and picks the first non-loopback address. // Pass node IP as hostname to make kubelet use the desired IP address. if len(options.NodeIP) > 0 { server.HostnameOverride = options.NodeIP } else { server.HostnameOverride = options.NodeName } server.AllowPrivileged = true server.RegisterNode = true server.Address = kubeAddress server.Port = uint(kubePort) server.ReadOnlyPort = 0 // no read only access server.CAdvisorPort = 0 // no unsecured cadvisor access server.HealthzPort = 0 // no unsecured healthz access server.ClusterDNS = dnsIP server.ClusterDomain = options.DNSDomain server.NetworkPluginName = options.NetworkConfig.NetworkPluginName server.HostNetworkSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HostPIDSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HostIPCSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HTTPCheckFrequency = 0 // no remote HTTP pod creation access server.FileCheckFrequency = time.Duration(fileCheckInterval) * time.Second server.PodInfraContainerImage = imageTemplate.ExpandOrDie("pod") server.CPUCFSQuota = true // enable cpu cfs quota enforcement by default // prevents kube from generating certs server.TLSCertFile = options.ServingInfo.ServerCert.CertFile server.TLSPrivateKeyFile = options.ServingInfo.ServerCert.KeyFile if value := cmdutil.Env("OPENSHIFT_CONTAINERIZED", ""); len(value) > 0 { server.Containerized = value == "true" } // resolve extended arguments // TODO: this should be done in config validation (along with the above) so we can provide // proper errors if err := cmdflags.Resolve(options.KubeletArguments, server.AddFlags); len(err) > 0 { return nil, errors.NewAggregate(err) } cfg, err := server.UnsecuredKubeletConfig() if err != nil { return nil, err } // provide any config overrides cfg.NodeName = options.NodeName cfg.StreamingConnectionIdleTimeout = 5 * time.Minute // TODO: should be set cfg.KubeClient = kubeClient cfg.DockerExecHandler = dockerExecHandler // Setup auth osClient, osClientConfig, err := configapi.GetOpenShiftClient(options.MasterKubeConfig) if err != nil { return nil, err } authnTTL, err := time.ParseDuration(options.AuthConfig.AuthenticationCacheTTL) if err != nil { return nil, err } authn, err := newAuthenticator(clientCAs, clientcmd.AnonymousClientConfig(*osClientConfig), authnTTL, options.AuthConfig.AuthenticationCacheSize) if err != nil { return nil, err } authzAttr, err := newAuthorizerAttributesGetter(options.NodeName) if err != nil { return nil, err } authzTTL, err := time.ParseDuration(options.AuthConfig.AuthorizationCacheTTL) if err != nil { return nil, err } authz, err := newAuthorizer(osClient, authzTTL, options.AuthConfig.AuthorizationCacheSize) if err != nil { return nil, err } cfg.Auth = kubelet.NewKubeletAuth(authn, authzAttr, authz) // Make sure the node doesn't think it is in standalone mode // This is required for the node to enforce nodeSelectors on pods, to set hostIP on pod status updates, etc cfg.StandaloneMode = false // TODO: could be cleaner if configapi.UseTLS(options.ServingInfo) { extraCerts, err := configapi.GetNamedCertificateMap(options.ServingInfo.NamedCertificates) if err != nil { return nil, err } cfg.TLSOptions = &kubelet.TLSOptions{ Config: crypto.SecureTLSConfig(&tls.Config{ // RequestClientCert lets us request certs, but allow requests without client certs // Verification is done by the authn layer ClientAuth: tls.RequestClientCert, ClientCAs: clientCAs, // Set SNI certificate func // Do not use NameToCertificate, since that requires certificates be included in the server's tlsConfig.Certificates list, // which we do not control when running with http.Server#ListenAndServeTLS GetCertificate: cmdutil.GetCertificateFunc(extraCerts), }), CertFile: options.ServingInfo.ServerCert.CertFile, KeyFile: options.ServingInfo.ServerCert.KeyFile, } } else { cfg.TLSOptions = nil } // Prepare cloud provider cloud, err := cloudprovider.InitCloudProvider(server.CloudProvider, server.CloudConfigFile) if err != nil { return nil, err } if cloud != nil { glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", server.CloudProvider, server.CloudConfigFile) } cfg.Cloud = cloud config := &NodeConfig{ BindAddress: options.ServingInfo.BindAddress, AllowDisabledDocker: options.AllowDisabledDocker, Client: kubeClient, VolumeDir: options.VolumeDirectory, KubeletServer: server, KubeletConfig: cfg, IPTablesSyncPeriod: options.IPTablesSyncPeriod, } return config, nil }
func BuildKubernetesNodeConfig(options configapi.NodeConfig, enableProxy, enableDNS bool) (*NodeConfig, error) { originClient, osClientConfig, err := configapi.GetOpenShiftClient(options.MasterKubeConfig) if err != nil { return nil, err } kubeClient, _, err := configapi.GetKubeClient(options.MasterKubeConfig) if err != nil { return nil, err } // Make a separate client for event reporting, to avoid event QPS blocking node calls eventClient, _, err := configapi.GetKubeClient(options.MasterKubeConfig) if err != nil { return nil, err } if options.NodeName == "localhost" { glog.Warningf(`Using "localhost" as node name will not resolve from all locations`) } clientCAs, err := kcrypto.CertPoolFromFile(options.ServingInfo.ClientCA) if err != nil { return nil, err } imageTemplate := variable.NewDefaultImageTemplate() imageTemplate.Format = options.ImageConfig.Format imageTemplate.Latest = options.ImageConfig.Latest var path string var fileCheckInterval int64 if options.PodManifestConfig != nil { path = options.PodManifestConfig.Path fileCheckInterval = options.PodManifestConfig.FileCheckIntervalSeconds } var dockerExecHandler dockertools.ExecHandler switch options.DockerConfig.ExecHandlerName { case configapi.DockerExecHandlerNative: dockerExecHandler = &dockertools.NativeExecHandler{} case configapi.DockerExecHandlerNsenter: dockerExecHandler = &dockertools.NsenterExecHandler{} } kubeAddressStr, kubePortStr, err := net.SplitHostPort(options.ServingInfo.BindAddress) if err != nil { return nil, fmt.Errorf("cannot parse node address: %v", err) } kubePort, err := strconv.Atoi(kubePortStr) if err != nil { return nil, fmt.Errorf("cannot parse node port: %v", err) } options.NetworkConfig.NetworkPluginName, err = validateAndGetNetworkPluginName(originClient, options.NetworkConfig.NetworkPluginName) if err != nil { return nil, err } // Defaults are tested in TestKubeletDefaults server := kubeletoptions.NewKubeletServer() // Adjust defaults server.Config = path server.RootDirectory = options.VolumeDirectory server.NodeIP = options.NodeIP server.HostnameOverride = options.NodeName server.AllowPrivileged = true server.RegisterNode = true server.Address = kubeAddressStr server.Port = uint(kubePort) server.ReadOnlyPort = 0 // no read only access server.CAdvisorPort = 0 // no unsecured cadvisor access server.HealthzPort = 0 // no unsecured healthz access server.HealthzBindAddress = "" // no unsecured healthz access server.ClusterDNS = options.DNSIP server.ClusterDomain = options.DNSDomain server.NetworkPluginName = options.NetworkConfig.NetworkPluginName server.HostNetworkSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HostPIDSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HostIPCSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HTTPCheckFrequency = unversioned.Duration{Duration: time.Duration(0)} // no remote HTTP pod creation access server.FileCheckFrequency = unversioned.Duration{Duration: time.Duration(fileCheckInterval) * time.Second} server.PodInfraContainerImage = imageTemplate.ExpandOrDie("pod") server.CPUCFSQuota = true // enable cpu cfs quota enforcement by default server.MaxPods = 110 server.SerializeImagePulls = false // disable serial image pulls by default if enableDNS { // if we are running local DNS, skydns will load the default recursive nameservers for us server.ResolverConfig = "" } if sdnplugin.IsOpenShiftNetworkPlugin(server.NetworkPluginName) { // set defaults for openshift-sdn server.HairpinMode = componentconfig.HairpinNone server.ConfigureCBR0 = false } // prevents kube from generating certs server.TLSCertFile = options.ServingInfo.ServerCert.CertFile server.TLSPrivateKeyFile = options.ServingInfo.ServerCert.KeyFile containerized := cmdutil.Env("OPENSHIFT_CONTAINERIZED", "") == "true" server.Containerized = containerized // resolve extended arguments // TODO: this should be done in config validation (along with the above) so we can provide // proper errors if err := cmdflags.Resolve(options.KubeletArguments, server.AddFlags); len(err) > 0 { return nil, kerrors.NewAggregate(err) } proxyconfig, err := buildKubeProxyConfig(options) if err != nil { return nil, err } cfg, err := kubeletapp.UnsecuredKubeletConfig(server) if err != nil { return nil, err } // provide any config overrides cfg.NodeName = options.NodeName cfg.KubeClient = clientadapter.FromUnversionedClient(kubeClient) cfg.EventClient = clientadapter.FromUnversionedClient(eventClient) cfg.DockerExecHandler = dockerExecHandler // Setup auth authnTTL, err := time.ParseDuration(options.AuthConfig.AuthenticationCacheTTL) if err != nil { return nil, err } authn, err := newAuthenticator(clientCAs, clientcmd.AnonymousClientConfig(osClientConfig), authnTTL, options.AuthConfig.AuthenticationCacheSize) if err != nil { return nil, err } authzAttr, err := newAuthorizerAttributesGetter(options.NodeName) if err != nil { return nil, err } authzTTL, err := time.ParseDuration(options.AuthConfig.AuthorizationCacheTTL) if err != nil { return nil, err } authz, err := newAuthorizer(originClient, authzTTL, options.AuthConfig.AuthorizationCacheSize) if err != nil { return nil, err } cfg.Auth = kubeletserver.NewKubeletAuth(authn, authzAttr, authz) // Make sure the node doesn't think it is in standalone mode // This is required for the node to enforce nodeSelectors on pods, to set hostIP on pod status updates, etc cfg.StandaloneMode = false // TODO: could be cleaner if configapi.UseTLS(options.ServingInfo) { extraCerts, err := configapi.GetNamedCertificateMap(options.ServingInfo.NamedCertificates) if err != nil { return nil, err } cfg.TLSOptions = &kubeletserver.TLSOptions{ Config: crypto.SecureTLSConfig(&tls.Config{ // RequestClientCert lets us request certs, but allow requests without client certs // Verification is done by the authn layer ClientAuth: tls.RequestClientCert, ClientCAs: clientCAs, // Set SNI certificate func // Do not use NameToCertificate, since that requires certificates be included in the server's tlsConfig.Certificates list, // which we do not control when running with http.Server#ListenAndServeTLS GetCertificate: cmdutil.GetCertificateFunc(extraCerts), }), CertFile: options.ServingInfo.ServerCert.CertFile, KeyFile: options.ServingInfo.ServerCert.KeyFile, } } else { cfg.TLSOptions = nil } // Prepare cloud provider cloud, err := cloudprovider.InitCloudProvider(server.CloudProvider, server.CloudConfigFile) if err != nil { return nil, err } if cloud != nil { glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", server.CloudProvider, server.CloudConfigFile) } cfg.Cloud = cloud iptablesSyncPeriod, err := time.ParseDuration(options.IPTablesSyncPeriod) if err != nil { return nil, fmt.Errorf("Cannot parse the provided ip-tables sync period (%s) : %v", options.IPTablesSyncPeriod, err) } sdnPlugin, err := sdnplugin.NewNodePlugin(options.NetworkConfig.NetworkPluginName, originClient, kubeClient, options.NodeName, options.NodeIP, iptablesSyncPeriod, options.NetworkConfig.MTU) if err != nil { return nil, fmt.Errorf("SDN initialization failed: %v", err) } if sdnPlugin != nil { cfg.NetworkPlugins = append(cfg.NetworkPlugins, sdnPlugin) } endpointFilter, err := sdnplugin.NewProxyPlugin(options.NetworkConfig.NetworkPluginName, originClient, kubeClient) if err != nil { return nil, fmt.Errorf("SDN proxy initialization failed: %v", err) } config := &NodeConfig{ BindAddress: options.ServingInfo.BindAddress, AllowDisabledDocker: options.AllowDisabledDocker, Containerized: containerized, Client: kubeClient, VolumeDir: options.VolumeDirectory, KubeletServer: server, KubeletConfig: cfg, ServicesReady: make(chan struct{}), ProxyConfig: proxyconfig, SDNPlugin: sdnPlugin, FilteringEndpointsHandler: endpointFilter, } if enableDNS { dnsConfig, err := dns.NewServerDefaults() if err != nil { return nil, fmt.Errorf("DNS configuration was not possible: %v", err) } if len(options.DNSIP) > 0 { dnsConfig.DnsAddr = options.DNSIP + ":53" } dnsConfig.Domain = server.ClusterDomain + "." dnsConfig.Local = "openshift.default.svc." + dnsConfig.Domain services, serviceStore := dns.NewCachedServiceAccessorAndStore() endpoints, endpointsStore := dns.NewCachedEndpointsAccessorAndStore() if !enableProxy { endpoints = kubeClient endpointsStore = nil } // TODO: use kubeletConfig.ResolverConfig as an argument to etcd in the event the // user sets it, instead of passing it to the kubelet. config.ServiceStore = serviceStore config.EndpointsStore = endpointsStore config.DNSServer = &dns.Server{ Config: dnsConfig, Services: services, Endpoints: endpoints, MetricsName: "node", } } return config, nil }
func runOAuthFlow(t *testing.T, clusterAdminClientConfig *restclient.Config, projectName string, oauthClientConfig *osincli.ClientConfig, authorizationCodes, authorizationErrors chan string, expectGrantSuccess, expectBuildSuccess bool) { oauthRuntimeClient, err := osincli.NewClient(oauthClientConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } oauthRuntimeClient.Transport = &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } directHTTPClient := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, }, } // make sure we're prompted for a password authorizeRequest := oauthRuntimeClient.NewAuthorizeRequest(osincli.CODE) authorizeURL := authorizeRequest.GetAuthorizeUrlWithParams("opaque-scope") authorizeHTTPRequest, err := http.NewRequest("GET", authorizeURL.String(), nil) if err != nil { t.Fatalf("unexpected error: %v", err) } authorizeHTTPRequest.Header.Add("X-CSRF-Token", "csrf-01") authorizeResponse, err := directHTTPClient.Do(authorizeHTTPRequest) if err != nil { t.Fatalf("unexpected error: %v", err) } if authorizeResponse.StatusCode != http.StatusUnauthorized { response, _ := httputil.DumpResponse(authorizeResponse, true) t.Fatalf("didn't get an unauthorized:\n %v", string(response)) } // first we should get a redirect to a grant flow authenticatedAuthorizeHTTPRequest1, err := http.NewRequest("GET", authorizeURL.String(), nil) authenticatedAuthorizeHTTPRequest1.Header.Add("X-CSRF-Token", "csrf-01") authenticatedAuthorizeHTTPRequest1.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("harold:any-pass"))) authentictedAuthorizeResponse1, err := directHTTPClient.Transport.RoundTrip(authenticatedAuthorizeHTTPRequest1) if err != nil { t.Fatalf("unexpected error: %v", err) } if authentictedAuthorizeResponse1.StatusCode != http.StatusFound { response, _ := httputil.DumpResponse(authentictedAuthorizeResponse1, true) t.Fatalf("unexpected status :\n %v", string(response)) } // second we get a webpage with a prompt. Yeah, this next bit gets nasty authenticatedAuthorizeHTTPRequest2, err := http.NewRequest("GET", clusterAdminClientConfig.Host+authentictedAuthorizeResponse1.Header.Get("Location"), nil) authenticatedAuthorizeHTTPRequest2.Header.Add("X-CSRF-Token", "csrf-01") authenticatedAuthorizeHTTPRequest2.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("harold:any-pass"))) authentictedAuthorizeResponse2, err := directHTTPClient.Transport.RoundTrip(authenticatedAuthorizeHTTPRequest2) if err != nil { t.Fatalf("unexpected error: %v", err) } if authentictedAuthorizeResponse2.StatusCode != http.StatusOK { response, _ := httputil.DumpResponse(authentictedAuthorizeResponse2, true) t.Fatalf("unexpected status :\n %v", string(response)) } // have to parse the page to get the csrf value. Yeah, that's nasty, I can't think of another way to do it without creating a new grant handler body, err := ioutil.ReadAll(authentictedAuthorizeResponse2.Body) if err != nil { t.Fatalf("unexpected error: %v", err) } if !expectGrantSuccess { if !strings.Contains(string(body), "requested illegal scopes") { t.Fatalf("missing expected message: %v", string(body)) } return } csrfMatches := grantCSRFRegex.FindStringSubmatch(string(body)) if len(csrfMatches) != 2 { response, _ := httputil.DumpResponse(authentictedAuthorizeResponse2, false) t.Fatalf("unexpected body :\n %v\n%v", string(response), string(body)) } thenMatches := grantThenRegex.FindStringSubmatch(string(body)) if len(thenMatches) != 2 { response, _ := httputil.DumpResponse(authentictedAuthorizeResponse2, false) t.Fatalf("unexpected body :\n %v\n%v", string(response), string(body)) } t.Logf("CSRF is %v", csrfMatches) t.Logf("then is %v", thenMatches) // third we respond and approve the grant, then let the transport follow redirects and give us the code postBody := strings.NewReader(url.Values(map[string][]string{ "then": {thenMatches[1]}, "csrf": {csrfMatches[1]}, "client_id": {oauthClientConfig.ClientId}, "user_name": {"harold"}, "scopes": {oauthClientConfig.Scope}, "redirect_uri": {clusterAdminClientConfig.Host}, "approve": {"true"}, }).Encode()) authenticatedAuthorizeHTTPRequest3, err := http.NewRequest("POST", clusterAdminClientConfig.Host+origin.OpenShiftApprovePrefix, postBody) authenticatedAuthorizeHTTPRequest3.Header.Set("Content-Type", "application/x-www-form-urlencoded") authenticatedAuthorizeHTTPRequest3.Header.Add("X-CSRF-Token", csrfMatches[1]) authenticatedAuthorizeHTTPRequest3.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("harold:any-pass"))) for i := range authentictedAuthorizeResponse2.Cookies() { cookie := authentictedAuthorizeResponse2.Cookies()[i] authenticatedAuthorizeHTTPRequest3.AddCookie(cookie) } authentictedAuthorizeResponse3, err := directHTTPClient.Transport.RoundTrip(authenticatedAuthorizeHTTPRequest3) if err != nil { t.Fatalf("unexpected error: %v", err) } if authentictedAuthorizeResponse3.StatusCode != http.StatusFound { response, _ := httputil.DumpResponse(authentictedAuthorizeResponse3, true) t.Fatalf("unexpected status :\n %v", string(response)) } // fourth, the grant redirects us again to have us send the code to the server authenticatedAuthorizeHTTPRequest4, err := http.NewRequest("GET", clusterAdminClientConfig.Host+authentictedAuthorizeResponse3.Header.Get("Location"), nil) authenticatedAuthorizeHTTPRequest4.Header.Add("X-CSRF-Token", "csrf-01") authenticatedAuthorizeHTTPRequest4.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("harold:any-pass"))) authentictedAuthorizeResponse4, err := directHTTPClient.Transport.RoundTrip(authenticatedAuthorizeHTTPRequest4) if err != nil { t.Fatalf("unexpected error: %v", err) } if authentictedAuthorizeResponse4.StatusCode != http.StatusFound { response, _ := httputil.DumpResponse(authentictedAuthorizeResponse4, true) t.Fatalf("unexpected status :\n %v", string(response)) } authenticatedAuthorizeHTTPRequest5, err := http.NewRequest("GET", authentictedAuthorizeResponse4.Header.Get("Location"), nil) authenticatedAuthorizeHTTPRequest5.Header.Add("X-CSRF-Token", "csrf-01") authenticatedAuthorizeHTTPRequest5.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("harold:any-pass"))) authentictedAuthorizeResponse5, err := directHTTPClient.Do(authenticatedAuthorizeHTTPRequest5) if err != nil { t.Fatalf("unexpected error: %v", err) } authorizationCode := "" select { case authorizationCode = <-authorizationCodes: case <-time.After(10 * time.Second): response, _ := httputil.DumpResponse(authentictedAuthorizeResponse5, true) t.Fatalf("didn't get a code:\n %v", string(response)) } accessRequest := oauthRuntimeClient.NewAccessRequest(osincli.AUTHORIZATION_CODE, &osincli.AuthorizeData{Code: authorizationCode}) accessData, err := accessRequest.GetToken() if err != nil { t.Fatalf("unexpected error: %v", err) } whoamiConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig) whoamiConfig.BearerToken = accessData.AccessToken whoamiClient, err := client.New(&whoamiConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } if _, err := whoamiClient.Builds(projectName).List(kapi.ListOptions{}); !kapierrors.IsForbidden(err) && !expectBuildSuccess { t.Fatalf("unexpected error: %v", err) } user, err := whoamiClient.Users().Get("~") if err != nil { t.Fatalf("unexpected error: %v", err) } if user.Name != "harold" { t.Fatalf("expected %v, got %v", "harold", user.Name) } }
func runOAuthFlow( t *testing.T, clusterAdminClientConfig *restclient.Config, projectName string, oauthClientConfig *osincli.ClientConfig, inputFilter htmlutil.InputFilterFunc, authorizationCodes chan string, authorizationErrors chan string, expectGrantSuccess bool, expectBuildSuccess bool, expectOperations []string, ) { drain(authorizationCodes) drain(authorizationErrors) oauthRuntimeClient, err := osincli.NewClient(oauthClientConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } testTransport := &basicAuthTransport{rt: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} oauthRuntimeClient.Transport = testTransport authorizeRequest := oauthRuntimeClient.NewAuthorizeRequest(osincli.CODE) req, err := http.NewRequest("GET", authorizeRequest.GetAuthorizeUrlWithParams("opaque-state").String(), nil) if err != nil { t.Fatalf("unexpected error: %v", err) } operations := []string{} jar, _ := cookiejar.New(nil) directHTTPClient := &http.Client{ Transport: testTransport, CheckRedirect: func(redirectReq *http.Request, via []*http.Request) error { glog.Infof("302 Location: %s", redirectReq.URL.String()) req = redirectReq operations = append(operations, "redirect to "+redirectReq.URL.Path) return nil }, Jar: jar, } for { glog.Infof("%s %s", req.Method, req.URL.String()) operations = append(operations, req.Method+" "+req.URL.Path) // Always set the csrf header req.Header.Set("X-CSRF-Token", "1") resp, err := directHTTPClient.Do(req) if err != nil { glog.Infof("%#v", operations) glog.Infof("%#v", jar) glog.Errorf("Error %v\n%#v\n%#v", err, err, resp) t.Errorf("Error %v\n%#v\n%#v", err, err, resp) return } defer resp.Body.Close() // Save the current URL for reference currentURL := req.URL if resp.StatusCode == 401 { // Set up a username and password once we're challenged testTransport.username = "******" testTransport.password = "******" operations = append(operations, "received challenge") continue } if resp.StatusCode != 200 { responseDump, _ := httputil.DumpResponse(resp, true) t.Errorf("Unexpected response %s", string(responseDump)) return } doc, err := html.Parse(resp.Body) if err != nil { t.Error(err) return } forms := htmlutil.GetElementsByTagName(doc, "form") // if there's a single form, submit it if len(forms) > 1 { t.Errorf("More than one form encountered: %d", len(forms)) return } if len(forms) == 0 { break } req, err = htmlutil.NewRequestFromForm(forms[0], currentURL, inputFilter) if err != nil { t.Error(err) return } operations = append(operations, "form") } authorizationCode := "" select { case authorizationCode = <-authorizationCodes: operations = append(operations, "code") case authorizationError := <-authorizationErrors: operations = append(operations, "error:"+authorizationError) case <-time.After(5 * time.Second): t.Error("didn't get a code or an error") } if len(authorizationCode) > 0 { accessRequest := oauthRuntimeClient.NewAccessRequest(osincli.AUTHORIZATION_CODE, &osincli.AuthorizeData{Code: authorizationCode}) accessData, err := accessRequest.GetToken() if err != nil { t.Errorf("unexpected error: %v", err) return } operations = append(operations, fmt.Sprintf("scope:%v", accessData.ResponseData["scope"])) whoamiConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig) whoamiConfig.BearerToken = accessData.AccessToken whoamiClient, err := client.New(&whoamiConfig) if err != nil { t.Errorf("unexpected error: %v", err) return } _, err = whoamiClient.Builds(projectName).List(kapi.ListOptions{}) if expectBuildSuccess && err != nil { t.Errorf("unexpected error: %v", err) return } if !expectBuildSuccess && !kapierrors.IsForbidden(err) { t.Errorf("expected forbidden error, got %v", err) return } user, err := whoamiClient.Users().Get("~") if err != nil { t.Errorf("unexpected error: %v", err) return } if user.Name != "harold" { t.Errorf("expected %v, got %v", "harold", user.Name) return } } if !reflect.DeepEqual(operations, expectOperations) { t.Errorf("Expected:\n%#v\nGot\n%#v", expectOperations, operations) } }
func BuildKubernetesNodeConfig(options configapi.NodeConfig) (*NodeConfig, error) { originClient, _, err := configapi.GetOpenShiftClient(options.MasterKubeConfig) if err != nil { return nil, err } kubeClient, _, err := configapi.GetKubeClient(options.MasterKubeConfig) if err != nil { return nil, err } // Make a separate client for event reporting, to avoid event QPS blocking node calls eventClient, _, err := configapi.GetKubeClient(options.MasterKubeConfig) if err != nil { return nil, err } if options.NodeName == "localhost" { glog.Warningf(`Using "localhost" as node name will not resolve from all locations`) } clientCAs, err := util.CertPoolFromFile(options.ServingInfo.ClientCA) if err != nil { return nil, err } imageTemplate := variable.NewDefaultImageTemplate() imageTemplate.Format = options.ImageConfig.Format imageTemplate.Latest = options.ImageConfig.Latest var path string var fileCheckInterval int64 if options.PodManifestConfig != nil { path = options.PodManifestConfig.Path fileCheckInterval = options.PodManifestConfig.FileCheckIntervalSeconds } var dockerExecHandler dockertools.ExecHandler switch options.DockerConfig.ExecHandlerName { case configapi.DockerExecHandlerNative: dockerExecHandler = &dockertools.NativeExecHandler{} case configapi.DockerExecHandlerNsenter: dockerExecHandler = &dockertools.NsenterExecHandler{} } kubeAddressStr, kubePortStr, err := net.SplitHostPort(options.ServingInfo.BindAddress) if err != nil { return nil, fmt.Errorf("cannot parse node address: %v", err) } kubePort, err := strconv.Atoi(kubePortStr) if err != nil { return nil, fmt.Errorf("cannot parse node port: %v", err) } // declare the OpenShift defaults from config server := kubeletoptions.NewKubeletServer() server.Config = path server.RootDirectory = options.VolumeDirectory server.NodeIP = options.NodeIP server.HostnameOverride = options.NodeName server.AllowPrivileged = true server.RegisterNode = true server.Address = kubeAddressStr server.Port = uint(kubePort) server.ReadOnlyPort = 0 // no read only access server.CAdvisorPort = 0 // no unsecured cadvisor access server.HealthzPort = 0 // no unsecured healthz access server.ClusterDNS = options.DNSIP server.ClusterDomain = options.DNSDomain server.NetworkPluginName = options.NetworkConfig.NetworkPluginName server.HostNetworkSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HostPIDSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HostIPCSources = strings.Join([]string{kubelettypes.ApiserverSource, kubelettypes.FileSource}, ",") server.HTTPCheckFrequency = unversioned.Duration{Duration: time.Duration(0)} // no remote HTTP pod creation access server.FileCheckFrequency = unversioned.Duration{Duration: time.Duration(fileCheckInterval) * time.Second} server.PodInfraContainerImage = imageTemplate.ExpandOrDie("pod") server.CPUCFSQuota = true // enable cpu cfs quota enforcement by default server.MaxPods = 110 // prevents kube from generating certs server.TLSCertFile = options.ServingInfo.ServerCert.CertFile server.TLSPrivateKeyFile = options.ServingInfo.ServerCert.KeyFile containerized := cmdutil.Env("OPENSHIFT_CONTAINERIZED", "") == "true" server.Containerized = containerized // resolve extended arguments // TODO: this should be done in config validation (along with the above) so we can provide // proper errors if err := cmdflags.Resolve(options.KubeletArguments, server.AddFlags); len(err) > 0 { return nil, kerrors.NewAggregate(err) } proxyconfig, err := buildKubeProxyConfig(options) if err != nil { return nil, err } cfg, err := kubeletapp.UnsecuredKubeletConfig(server) if err != nil { return nil, err } // Replace the standard k8s emptyDir volume plugin with a wrapper version // which offers XFS quota functionality, but only if the node config // specifies an empty dir quota to apply to projects: if options.VolumeConfig.LocalQuota.PerFSGroup != nil { glog.V(2).Info("Replacing empty-dir volume plugin with quota wrapper") wrappedEmptyDirPlugin := false quotaApplicator, err := empty_dir.NewQuotaApplicator(options.VolumeDirectory) if err != nil { return nil, err } // Create a volume spec with emptyDir we can use to search for the // emptyDir plugin with CanSupport: emptyDirSpec := &volume.Spec{ Volume: &kapi.Volume{ VolumeSource: kapi.VolumeSource{ EmptyDir: &kapi.EmptyDirVolumeSource{}, }, }, } for idx, plugin := range cfg.VolumePlugins { // Can't really do type checking or use a constant here as they are not exported: if plugin.CanSupport(emptyDirSpec) { wrapper := empty_dir.EmptyDirQuotaPlugin{ Wrapped: plugin, Quota: *options.VolumeConfig.LocalQuota.PerFSGroup, QuotaApplicator: quotaApplicator, } cfg.VolumePlugins[idx] = &wrapper wrappedEmptyDirPlugin = true } } // Because we can't look for the k8s emptyDir plugin by any means that would // survive a refactor, error out if we couldn't find it: if !wrappedEmptyDirPlugin { return nil, errors.New("unable to wrap emptyDir volume plugin for quota support") } } else { glog.V(2).Info("Skipping replacement of empty-dir volume plugin with quota wrapper, no local fsGroup quota specified") } // provide any config overrides cfg.NodeName = options.NodeName cfg.KubeClient = internalclientset.FromUnversionedClient(kubeClient) cfg.EventClient = internalclientset.FromUnversionedClient(eventClient) cfg.DockerExecHandler = dockerExecHandler // docker-in-docker (dind) deployments are used for testing // networking plugins. Running openshift under dind won't work // with the real oom adjuster due to the state of the cgroups path // in a dind container that uses systemd for init. Similarly, // cgroup manipulation of the nested docker daemon doesn't work // properly under centos/rhel and should be disabled by setting // the name of the container to an empty string. // // This workaround should become unnecessary once user namespaces if value := cmdutil.Env("OPENSHIFT_DIND", ""); value == "true" { glog.Warningf("Using FakeOOMAdjuster for docker-in-docker compatibility") cfg.OOMAdjuster = oom.NewFakeOOMAdjuster() } // Setup auth osClient, osClientConfig, err := configapi.GetOpenShiftClient(options.MasterKubeConfig) if err != nil { return nil, err } authnTTL, err := time.ParseDuration(options.AuthConfig.AuthenticationCacheTTL) if err != nil { return nil, err } authn, err := newAuthenticator(clientCAs, clientcmd.AnonymousClientConfig(osClientConfig), authnTTL, options.AuthConfig.AuthenticationCacheSize) if err != nil { return nil, err } authzAttr, err := newAuthorizerAttributesGetter(options.NodeName) if err != nil { return nil, err } authzTTL, err := time.ParseDuration(options.AuthConfig.AuthorizationCacheTTL) if err != nil { return nil, err } authz, err := newAuthorizer(osClient, authzTTL, options.AuthConfig.AuthorizationCacheSize) if err != nil { return nil, err } cfg.Auth = kubeletserver.NewKubeletAuth(authn, authzAttr, authz) // Make sure the node doesn't think it is in standalone mode // This is required for the node to enforce nodeSelectors on pods, to set hostIP on pod status updates, etc cfg.StandaloneMode = false // TODO: could be cleaner if configapi.UseTLS(options.ServingInfo) { extraCerts, err := configapi.GetNamedCertificateMap(options.ServingInfo.NamedCertificates) if err != nil { return nil, err } cfg.TLSOptions = &kubeletserver.TLSOptions{ Config: crypto.SecureTLSConfig(&tls.Config{ // RequestClientCert lets us request certs, but allow requests without client certs // Verification is done by the authn layer ClientAuth: tls.RequestClientCert, ClientCAs: clientCAs, // Set SNI certificate func // Do not use NameToCertificate, since that requires certificates be included in the server's tlsConfig.Certificates list, // which we do not control when running with http.Server#ListenAndServeTLS GetCertificate: cmdutil.GetCertificateFunc(extraCerts), }), CertFile: options.ServingInfo.ServerCert.CertFile, KeyFile: options.ServingInfo.ServerCert.KeyFile, } } else { cfg.TLSOptions = nil } // Prepare cloud provider cloud, err := cloudprovider.InitCloudProvider(server.CloudProvider, server.CloudConfigFile) if err != nil { return nil, err } if cloud != nil { glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", server.CloudProvider, server.CloudConfigFile) } cfg.Cloud = cloud sdnPlugin, endpointFilter, err := factory.NewPlugin(options.NetworkConfig.NetworkPluginName, originClient, kubeClient, options.NodeName, options.NodeIP) if err != nil { return nil, fmt.Errorf("SDN initialization failed: %v", err) } if sdnPlugin != nil { cfg.NetworkPlugins = append(cfg.NetworkPlugins, sdnPlugin) } config := &NodeConfig{ BindAddress: options.ServingInfo.BindAddress, AllowDisabledDocker: options.AllowDisabledDocker, Containerized: containerized, Client: kubeClient, VolumeDir: options.VolumeDirectory, KubeletServer: server, KubeletConfig: cfg, ProxyConfig: proxyconfig, MTU: options.NetworkConfig.MTU, SDNPlugin: sdnPlugin, FilteringEndpointsHandler: endpointFilter, } return config, nil }