func GetBootstrapServiceAccountProjectRoleBindings(namespace string) []authorizationapi.RoleBinding { return []authorizationapi.RoleBinding{ { ObjectMeta: kapi.ObjectMeta{ Name: ImagePullerRoleBindingName, Namespace: namespace, }, RoleRef: kapi.ObjectReference{ Name: ImagePullerRoleName, }, Groups: util.NewStringSet(serviceaccount.MakeNamespaceGroupName(namespace)), }, { ObjectMeta: kapi.ObjectMeta{ Name: ImageBuilderRoleBindingName, Namespace: namespace, }, RoleRef: kapi.ObjectReference{ Name: ImageBuilderRoleName, }, Users: util.NewStringSet(serviceaccount.MakeUsername(namespace, BuilderServiceAccountName)), }, { ObjectMeta: kapi.ObjectMeta{ Name: DeployerRoleBindingName, Namespace: namespace, }, RoleRef: kapi.ObjectReference{ Name: DeployerRoleName, }, Users: util.NewStringSet(serviceaccount.MakeUsername(namespace, DeployerServiceAccountName)), }, } }
func getExpectedAccess() (map[string][]string, map[string][]string) { groups := map[string][]string{ SecurityContextConstraintPrivileged: {ClusterAdminGroup, NodesGroup}, SecurityContextConstraintsAnyUID: {ClusterAdminGroup}, SecurityContextConstraintRestricted: {AuthenticatedGroup}, } buildControllerUsername := serviceaccount.MakeUsername(DefaultOpenShiftInfraNamespace, InfraBuildControllerServiceAccountName) pvControllerUsername := serviceaccount.MakeUsername(DefaultOpenShiftInfraNamespace, InfraPersistentVolumeBinderControllerServiceAccountName) users := map[string][]string{ SecurityContextConstraintPrivileged: {buildControllerUsername}, SecurityContextConstraintHostMountAndAnyUID: {pvControllerUsername}, } return groups, users }
// GetBoostrapSCCAccess provides the default set of access that should be passed to GetBootstrapSecurityContextConstraints. func GetBoostrapSCCAccess(infraNamespace string) (map[string][]string, map[string][]string) { groups := map[string][]string{ SecurityContextConstraintPrivileged: {ClusterAdminGroup, NodesGroup}, SecurityContextConstraintsAnyUID: {ClusterAdminGroup}, SecurityContextConstraintRestricted: {AuthenticatedGroup}, } buildControllerUsername := serviceaccount.MakeUsername(infraNamespace, InfraBuildControllerServiceAccountName) pvRecyclerControllerUsername := serviceaccount.MakeUsername(infraNamespace, InfraPersistentVolumeRecyclerControllerServiceAccountName) users := map[string][]string{ SecurityContextConstraintPrivileged: {buildControllerUsername}, SecurityContextConstraintHostMountAndAnyUID: {pvRecyclerControllerUsername}, } return groups, users }
func (o *RoleModificationOptions) CompleteUserWithSA(f *clientcmd.Factory, args []string, saNames util.StringList) error { if (len(args) < 2) && (len(saNames) == 0) { return errors.New("You must specify at least two arguments: <role> <user> [user]...") } o.RoleName = args[0] if len(args) > 1 { o.Users = append(o.Users, args[1:]...) } osClient, _, err := f.Clients() if err != nil { return err } roleBindingNamespace, _, err := f.DefaultNamespace() if err != nil { return err } o.RoleBindingAccessor = NewLocalRoleBindingAccessor(roleBindingNamespace, osClient) for _, sa := range saNames { o.Users = append(o.Users, serviceaccount.MakeUsername(roleBindingNamespace, sa)) } return nil }
// StringSubjectsFor returns users and groups for comparison against user.Info. currentNamespace is used to // to create usernames for service accounts where namespace=="". func StringSubjectsFor(currentNamespace string, subjects []kapi.ObjectReference) ([]string, []string) { // these MUST be nil to indicate empty var users, groups []string for _, subject := range subjects { switch subject.Kind { case ServiceAccountKind: namespace := currentNamespace if len(subject.Namespace) > 0 { namespace = subject.Namespace } if len(namespace) > 0 { users = append(users, serviceaccount.MakeUsername(namespace, subject.Name)) } case UserKind, SystemUserKind: users = append(users, subject.Name) case GroupKind, SystemGroupKind: groups = append(groups, subject.Name) } } return users, groups }
// SubjectsContainUser returns true if the provided subjects contain the named user. currentNamespace // is used to identify service accounts that are defined in a relative fashion. func SubjectsContainUser(subjects []kapi.ObjectReference, currentNamespace string, user string) bool { if !strings.HasPrefix(user, serviceaccount.ServiceAccountUsernamePrefix) { for _, subject := range subjects { switch subject.Kind { case UserKind, SystemUserKind: if user == subject.Name { return true } } } return false } for _, subject := range subjects { switch subject.Kind { case ServiceAccountKind: namespace := currentNamespace if len(subject.Namespace) > 0 { namespace = subject.Namespace } if len(namespace) == 0 { continue } if user == serviceaccount.MakeUsername(namespace, subject.Name) { return true } case UserKind, SystemUserKind: if user == subject.Name { return true } } } return false }
// ensureOpenShiftInfraNamespace is called as part of global policy initialization to ensure infra namespace exists func (c *MasterConfig) ensureOpenShiftInfraNamespace() { ns := c.Options.PolicyConfig.OpenShiftInfrastructureNamespace // Ensure namespace exists _, err := c.KubeClient().Namespaces().Create(&kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: ns}}) if err != nil && !kapierror.IsAlreadyExists(err) { glog.Errorf("Error creating namespace %s: %v", ns, err) } // Ensure service accounts exist serviceAccounts := []string{c.BuildControllerServiceAccount, c.DeploymentControllerServiceAccount, c.ReplicationControllerServiceAccount} for _, serviceAccountName := range serviceAccounts { _, err := c.KubeClient().ServiceAccounts(ns).Create(&kapi.ServiceAccount{ObjectMeta: kapi.ObjectMeta{Name: serviceAccountName}}) if err != nil && !kapierror.IsAlreadyExists(err) { glog.Errorf("Error creating service account %s/%s: %v", ns, serviceAccountName, err) } } // Ensure service account cluster role bindings exist clusterRolesToUsernames := map[string][]string{ bootstrappolicy.BuildControllerRoleName: {serviceaccount.MakeUsername(ns, c.BuildControllerServiceAccount)}, bootstrappolicy.DeploymentControllerRoleName: {serviceaccount.MakeUsername(ns, c.DeploymentControllerServiceAccount)}, bootstrappolicy.ReplicationControllerRoleName: {serviceaccount.MakeUsername(ns, c.ReplicationControllerServiceAccount)}, } roleAccessor := policy.NewClusterRoleBindingAccessor(c.ServiceAccountRoleBindingClient()) for clusterRole, usernames := range clusterRolesToUsernames { addRole := &policy.RoleModificationOptions{ RoleName: clusterRole, RoleBindingAccessor: roleAccessor, Users: usernames, } if err := addRole.AddRole(); err != nil { glog.Errorf("Could not add %v users to the %v cluster role: %v\n", ns, usernames, clusterRole, err) } else { glog.V(2).Infof("Added %v users to the %v cluster role: %v\n", usernames, clusterRole, err) } } }
func appliesToUser(user user.Info, subject rbac.Subject) (bool, error) { switch subject.Kind { case rbac.UserKind: return subject.Name == rbac.UserAll || user.GetName() == subject.Name, nil case rbac.GroupKind: return has(user.GetGroups(), subject.Name), nil case rbac.ServiceAccountKind: if subject.Namespace == "" { return false, fmt.Errorf("subject of kind service account without specified namespace") } return serviceaccount.MakeUsername(subject.Namespace, subject.Name) == user.GetName(), nil default: return false, fmt.Errorf("unknown subject kind: %s", subject.Kind) } }
func TestMakeSplitUsername(t *testing.T) { username := serviceaccount.MakeUsername("ns", "name") ns, name, err := serviceaccount.SplitUsername(username) if err != nil { t.Errorf("Unexpected error %v", err) } if ns != "ns" || name != "name" { t.Errorf("Expected ns/name, got %s/%s", ns, name) } invalid := []string{"test", "system:serviceaccount", "system:serviceaccount:", "system:serviceaccount:ns", "system:serviceaccount:ns:name:extra"} for _, n := range invalid { _, _, err := serviceaccount.SplitUsername("test") if err == nil { t.Errorf("Expected error for %s", n) } } }
func (c *MasterConfig) ensureDefaultSecurityContextConstraints() { sccList, err := c.KubeClient().SecurityContextConstraints().List(labels.Everything(), fields.Everything()) if err != nil { glog.Errorf("Unable to initialize security context constraints: %v. This may prevent the creation of pods", err) return } if len(sccList.Items) > 0 { return } glog.Infof("No security context constraints detected, adding defaults") ns := c.Options.PolicyConfig.OpenShiftInfrastructureNamespace buildControllerUsername := serviceaccount.MakeUsername(ns, c.BuildControllerServiceAccount) for _, scc := range bootstrappolicy.GetBootstrapSecurityContextConstraints(buildControllerUsername) { _, err = c.KubeClient().SecurityContextConstraints().Create(&scc) if err != nil { glog.Errorf("Unable to create default security context constraint %s. Got error: %v", scc.Name, err) } } }
func appliesToUser(user user.Info, subject rbac.Subject, namespace string) bool { switch subject.Kind { case rbac.UserKind: return subject.Name == rbac.UserAll || user.GetName() == subject.Name case rbac.GroupKind: return has(user.GetGroups(), subject.Name) case rbac.ServiceAccountKind: // default the namespace to namespace we're working in if its available. This allows rolebindings that reference // SAs in th local namespace to avoid having to qualify them. saNamespace := namespace if len(subject.Namespace) > 0 { saNamespace = subject.Namespace } if len(saNamespace) == 0 { return false } return serviceaccount.MakeUsername(saNamespace, subject.Name) == user.GetName() default: return false } }
func TestSAAsOAuthClient(t *testing.T) { testutil.RequireEtcd(t) _, clusterAdminKubeConfig, err := testserver.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } authorizationCodes := make(chan string, 1) authorizationErrors := make(chan string, 1) oauthServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { t.Logf("fake pod server got %v", req.URL) if code := req.URL.Query().Get("code"); len(code) > 0 { authorizationCodes <- code } if err := req.URL.Query().Get("error"); len(err) > 0 { authorizationErrors <- err } })) defer oauthServer.Close() clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(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" if _, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projectName, "harold"); err != nil { t.Fatalf("unexpected error: %v", err) } if err := testserver.WaitForServiceAccounts(clusterAdminKubeClient, projectName, []string{"default"}); err != nil { t.Fatalf("unexpected error: %v", err) } // get the SA ready with redirect URIs and secret annotations var defaultSA *kapi.ServiceAccount // retry this a couple times. We seem to be flaking on update conflicts and missing secrets all together err = kclient.RetryOnConflict(kclient.DefaultRetry, func() error { defaultSA, err = clusterAdminKubeClient.ServiceAccounts(projectName).Get("default") if err != nil { return err } if defaultSA.Annotations == nil { defaultSA.Annotations = map[string]string{} } defaultSA.Annotations[saoauth.OAuthRedirectURISecretAnnotationPrefix+"one"] = oauthServer.URL defaultSA.Annotations[saoauth.OAuthWantChallengesAnnotationPrefix] = "true" defaultSA, err = clusterAdminKubeClient.ServiceAccounts(projectName).Update(defaultSA) return err }) if err != nil { t.Fatalf("unexpected error: %v", err) } var oauthSecret *kapi.Secret // retry this a couple times. We seem to be flaking on update conflicts and missing secrets all together err = wait.PollImmediate(30*time.Millisecond, 10*time.Second, func() (done bool, err error) { allSecrets, err := clusterAdminKubeClient.Secrets(projectName).List(kapi.ListOptions{}) if err != nil { return false, err } for i := range allSecrets.Items { secret := allSecrets.Items[i] if serviceaccount.IsServiceAccountToken(&secret, defaultSA) { oauthSecret = &secret return true, nil } } return false, nil }) if err != nil { t.Fatalf("unexpected error: %v", err) } oauthClientConfig := &osincli.ClientConfig{ ClientId: serviceaccount.MakeUsername(defaultSA.Namespace, defaultSA.Name), ClientSecret: string(oauthSecret.Data[kapi.ServiceAccountTokenKey]), AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize", TokenUrl: clusterAdminClientConfig.Host + "/oauth/token", RedirectUrl: oauthServer.URL, Scope: scope.Join([]string{"user:info", "role:edit:" + projectName}), SendClientSecretInParams: true, } runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, authorizationCodes, authorizationErrors, true, true) clusterAdminClient.OAuthClientAuthorizations().Delete("harold:" + oauthClientConfig.ClientId) oauthClientConfig = &osincli.ClientConfig{ ClientId: serviceaccount.MakeUsername(defaultSA.Namespace, defaultSA.Name), ClientSecret: string(oauthSecret.Data[kapi.ServiceAccountTokenKey]), AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize", TokenUrl: clusterAdminClientConfig.Host + "/oauth/token", RedirectUrl: oauthServer.URL, Scope: scope.Join([]string{"user:info", "role:edit:other-ns"}), SendClientSecretInParams: true, } runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, authorizationCodes, authorizationErrors, false, false) clusterAdminClient.OAuthClientAuthorizations().Delete("harold:" + oauthClientConfig.ClientId) oauthClientConfig = &osincli.ClientConfig{ ClientId: serviceaccount.MakeUsername(defaultSA.Namespace, defaultSA.Name), ClientSecret: string(oauthSecret.Data[kapi.ServiceAccountTokenKey]), AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize", TokenUrl: clusterAdminClientConfig.Host + "/oauth/token", RedirectUrl: oauthServer.URL, Scope: scope.Join([]string{"user:info"}), SendClientSecretInParams: true, } runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, authorizationCodes, authorizationErrors, true, false) clusterAdminClient.OAuthClientAuthorizations().Delete("harold:" + oauthClientConfig.ClientId) }
func TestImpersonateIsForbidden(t *testing.T) { framework.DeleteAllEtcdKeys() var m *master.Master s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { m.Handler.ServeHTTP(w, req) })) defer s.Close() masterConfig := framework.NewIntegrationTestMasterConfig() masterConfig.Authenticator = getTestTokenAuth() masterConfig.Authorizer = impersonateAuthorizer{} m, err := master.New(masterConfig) if err != nil { t.Fatalf("error in bringing up the master: %v", err) } transport := http.DefaultTransport // bob can't perform actions himself for _, r := range getTestRequests() { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all of bob's actions to return Forbidden if resp.StatusCode != http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected not status Forbidden, but got %s", resp.Status) } }() } // bob can impersonate alice to do other things for _, r := range getTestRequests() { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", "alice") func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all the requests to be allowed, don't care what they actually do if resp.StatusCode == http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode) } }() } // alice can't impersonate bob for _, r := range getTestRequests() { token := AliceToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", "bob") func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all of bob's actions to return Forbidden if resp.StatusCode != http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected not status Forbidden, but got %s", resp.Status) } }() } // alice can impersonate a service account for _, r := range getTestRequests() { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", serviceaccount.MakeUsername("default", "default")) func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all the requests to be allowed, don't care what they actually do if resp.StatusCode == http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode) } }() } }
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 TestImpersonateIsForbidden(t *testing.T) { // Set up a master masterConfig := framework.NewIntegrationTestMasterConfig() masterConfig.Authenticator = getTestTokenAuth() masterConfig.Authorizer = impersonateAuthorizer{} _, s := framework.RunAMaster(masterConfig) defer s.Close() ns := framework.CreateTestingNamespace("auth-impersonate-forbidden", s, t) defer framework.DeleteTestingNamespace(ns, s, t) transport := http.DefaultTransport // bob can't perform actions himself for _, r := range getTestRequests(ns.Name) { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all of bob's actions to return Forbidden if resp.StatusCode != http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected not status Forbidden, but got %s", resp.Status) } }() } // bob can impersonate alice to do other things for _, r := range getTestRequests(ns.Name) { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", "alice") func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all the requests to be allowed, don't care what they actually do if resp.StatusCode == http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode) } }() } // alice can't impersonate bob for _, r := range getTestRequests(ns.Name) { token := AliceToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", "bob") func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all of bob's actions to return Forbidden if resp.StatusCode != http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected not status Forbidden, but got %s", resp.Status) } }() } // alice can impersonate a service account for _, r := range getTestRequests(ns.Name) { token := BobToken bodyBytes := bytes.NewReader([]byte(r.body)) req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes) if err != nil { t.Fatalf("unexpected error: %v", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) req.Header.Set("Impersonate-User", serviceaccount.MakeUsername("default", "default")) func() { resp, err := transport.RoundTrip(req) defer resp.Body.Close() if err != nil { t.Logf("case %v", r) t.Fatalf("unexpected error: %v", err) } // Expect all the requests to be allowed, don't care what they actually do if resp.StatusCode == http.StatusForbidden { t.Logf("case %v", r) t.Errorf("Expected status not %v, but got %v", http.StatusForbidden, resp.StatusCode) } }() } }
// InstallRouter installs a default router on the OpenShift server func (h *Helper) InstallRouter(kubeClient kclient.Interface, f *clientcmd.Factory, configDir, images, hostIP string, portForwarding bool, out io.Writer) error { _, err := kubeClient.Services(DefaultNamespace).Get(SvcRouter) if err == nil { // Router service already exists, nothing to do return nil } if !apierrors.IsNotFound(err) { return errors.NewError("error retrieving router service").WithCause(err).WithDetails(h.OriginLog()) } masterDir := filepath.Join(configDir, "master") // Create service account for router routerSA := &kapi.ServiceAccount{} routerSA.Name = "router" _, err = kubeClient.ServiceAccounts("default").Create(routerSA) if err != nil { return errors.NewError("cannot create router service account").WithCause(err).WithDetails(h.OriginLog()) } // Add router SA to privileged SCC privilegedSCC, err := kubeClient.SecurityContextConstraints().Get("privileged") if err != nil { return errors.NewError("cannot retrieve privileged SCC").WithCause(err).WithDetails(h.OriginLog()) } privilegedSCC.Users = append(privilegedSCC.Users, serviceaccount.MakeUsername("default", "router")) _, err = kubeClient.SecurityContextConstraints().Update(privilegedSCC) if err != nil { return errors.NewError("cannot update privileged SCC").WithCause(err).WithDetails(h.OriginLog()) } // Create router cert cmdOutput := &bytes.Buffer{} createCertOptions := &admin.CreateServerCertOptions{ SignerCertOptions: &admin.SignerCertOptions{ CertFile: filepath.Join(masterDir, "ca.crt"), KeyFile: filepath.Join(masterDir, "ca.key"), SerialFile: filepath.Join(masterDir, "ca.serial.txt"), }, Overwrite: true, Hostnames: []string{fmt.Sprintf("%s.xip.io", hostIP)}, CertFile: filepath.Join(masterDir, "router.crt"), KeyFile: filepath.Join(masterDir, "router.key"), Output: cmdOutput, } _, err = createCertOptions.CreateServerCert() if err != nil { return errors.NewError("cannot create router cert").WithCause(err) } err = catFiles(filepath.Join(masterDir, "router.pem"), filepath.Join(masterDir, "router.crt"), filepath.Join(masterDir, "router.key"), filepath.Join(masterDir, "ca.crt")) if err != nil { return err } imageTemplate := variable.NewDefaultImageTemplate() imageTemplate.Format = images cfg := &router.RouterConfig{ Name: "router", Type: "haproxy-router", ImageTemplate: imageTemplate, Ports: "80:80,443:443", Replicas: 1, Labels: "router=<name>", Credentials: filepath.Join(masterDir, "admin.kubeconfig"), DefaultCertificate: filepath.Join(masterDir, "router.pem"), StatsPort: 1936, StatsUsername: "******", HostNetwork: !portForwarding, HostPorts: true, ServiceAccount: "router", } output := &bytes.Buffer{} cmd := router.NewCmdRouter(f, "", "router", out) cmd.SetOutput(output) err = router.RunCmdRouter(f, cmd, output, cfg, []string{}) glog.V(4).Infof("Router command output:\n%s", output.String()) if err != nil { return errors.NewError("cannot install router").WithCause(err).WithDetails(h.OriginLog()) } return nil }
func (c *MasterConfig) impersonationFilter(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedSubject := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(requestedSubject) == 0 { handler.ServeHTTP(w, req) return } resource, namespace, name, err := parseRequestedSubject(requestedSubject) if err != nil { forbidden(err.Error(), nil, w, req) return } ctx, exists := c.RequestContextMapper.Get(req) if !exists { forbidden("context not found", nil, w, req) return } actingAsAttributes := &authorizer.DefaultAuthorizationAttributes{ Verb: "impersonate", APIGroup: resource.Group, Resource: resource.Resource, ResourceName: name, } authCheckCtx := kapi.WithNamespace(ctx, namespace) allowed, reason, err := c.Authorizer.Authorize(authCheckCtx, actingAsAttributes) if err != nil { forbidden(err.Error(), actingAsAttributes, w, req) return } if !allowed { forbidden(reason, actingAsAttributes, w, req) return } var extra map[string][]string if requestScopes, ok := req.Header[authenticationapi.ImpersonateUserScopeHeader]; ok { extra = map[string][]string{authorizationapi.ScopesKey: requestScopes} } switch resource { case kapi.Resource(authorizationapi.ServiceAccountResource): newUser := &user.DefaultInfo{ Name: serviceaccount.MakeUsername(namespace, name), Groups: serviceaccount.MakeGroupNames(namespace, name), Extra: extra, } newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup) c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) case userapi.Resource(authorizationapi.UserResource): newUser := &user.DefaultInfo{ Name: name, Extra: extra, } groups, err := c.GroupCache.GroupsFor(name) if err == nil { for _, group := range groups { newUser.Groups = append(newUser.Groups, group.Name) } } newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup, bootstrappolicy.AuthenticatedOAuthGroup) c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) case userapi.Resource(authorizationapi.SystemUserResource): newUser := &user.DefaultInfo{ Name: name, Extra: extra, } if name == bootstrappolicy.UnauthenticatedUsername { newUser.Groups = append(newUser.Groups, bootstrappolicy.UnauthenticatedGroup) } else { newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup) } c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) default: forbidden(fmt.Sprintf("%v is an unhandled resource for acting-as", resource), nil, w, req) return } newCtx, _ := c.RequestContextMapper.Get(req) oldUser, _ := kapi.UserFrom(ctx) newUser, _ := kapi.UserFrom(newCtx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }
func (c *MasterConfig) impersonationFilter(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedUser := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(requestedUser) == 0 { handler.ServeHTTP(w, req) return } subjects := authorizationapi.BuildSubjects([]string{requestedUser}, req.Header[authenticationapi.ImpersonateGroupHeader], // validates whether the usernames are regular users or system users uservalidation.ValidateUserName, // validates group names are regular groups or system groups uservalidation.ValidateGroupName) ctx, exists := c.RequestContextMapper.Get(req) if !exists { forbidden("context not found", nil, w, req) return } // if groups are not specified, then we need to look them up differently depending on the type of user // if they are specified, then they are the authority groupsSpecified := len(req.Header[authenticationapi.ImpersonateGroupHeader]) > 0 // make sure we're allowed to impersonate each subject. While we're iterating through, start building username // and group information username := "" groups := []string{} for _, subject := range subjects { actingAsAttributes := &authorizer.DefaultAuthorizationAttributes{ Verb: "impersonate", } switch subject.GetObjectKind().GroupVersionKind().GroupKind() { case userapi.Kind(authorizationapi.GroupKind): actingAsAttributes.APIGroup = userapi.GroupName actingAsAttributes.Resource = authorizationapi.GroupResource actingAsAttributes.ResourceName = subject.Name groups = append(groups, subject.Name) case userapi.Kind(authorizationapi.SystemGroupKind): actingAsAttributes.APIGroup = userapi.GroupName actingAsAttributes.Resource = authorizationapi.SystemGroupResource actingAsAttributes.ResourceName = subject.Name groups = append(groups, subject.Name) case userapi.Kind(authorizationapi.UserKind): actingAsAttributes.APIGroup = userapi.GroupName actingAsAttributes.Resource = authorizationapi.UserResource actingAsAttributes.ResourceName = subject.Name username = subject.Name if !groupsSpecified { if actualGroups, err := c.GroupCache.GroupsFor(subject.Name); err == nil { for _, group := range actualGroups { groups = append(groups, group.Name) } } groups = append(groups, bootstrappolicy.AuthenticatedGroup, bootstrappolicy.AuthenticatedOAuthGroup) } case userapi.Kind(authorizationapi.SystemUserKind): actingAsAttributes.APIGroup = userapi.GroupName actingAsAttributes.Resource = authorizationapi.SystemUserResource actingAsAttributes.ResourceName = subject.Name username = subject.Name if !groupsSpecified { if subject.Name == bootstrappolicy.UnauthenticatedUsername { groups = append(groups, bootstrappolicy.UnauthenticatedGroup) } else { groups = append(groups, bootstrappolicy.AuthenticatedGroup) } } case kapi.Kind(authorizationapi.ServiceAccountKind): actingAsAttributes.APIGroup = kapi.GroupName actingAsAttributes.Resource = authorizationapi.ServiceAccountResource actingAsAttributes.ResourceName = subject.Name username = serviceaccount.MakeUsername(subject.Namespace, subject.Name) if !groupsSpecified { groups = append(serviceaccount.MakeGroupNames(subject.Namespace, subject.Name), bootstrappolicy.AuthenticatedGroup) } default: forbidden(fmt.Sprintf("unknown subject type: %v", subject), actingAsAttributes, w, req) return } authCheckCtx := kapi.WithNamespace(ctx, subject.Namespace) allowed, reason, err := c.Authorizer.Authorize(authCheckCtx, actingAsAttributes) if err != nil { forbidden(err.Error(), actingAsAttributes, w, req) return } if !allowed { forbidden(reason, actingAsAttributes, w, req) return } } var extra map[string][]string if requestScopes, ok := req.Header[authenticationapi.ImpersonateUserScopeHeader]; ok { extra = map[string][]string{authorizationapi.ScopesKey: requestScopes} } newUser := &user.DefaultInfo{ Name: username, Groups: groups, Extra: extra, } c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) oldUser, _ := kapi.UserFrom(ctx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }
// config returns a complete clientConfig for constructing clients. This is separate in anticipation of composition // which means that not all clientsets are known here func (b SAControllerClientBuilder) Config(name string) (*restclient.Config, error) { clientConfig := restclient.AnonymousClientConfig(b.ClientConfig) // we need the SA UID to find a matching SA token sa, err := b.CoreClient.ServiceAccounts(b.Namespace).Get(name, metav1.GetOptions{}) if err != nil && !apierrors.IsNotFound(err) { return nil, err } else if apierrors.IsNotFound(err) { // check to see if the namespace exists. If it isn't a NotFound, just try to create the SA. // It'll probably fail, but perhaps that will have a better message. if _, err := b.CoreClient.Namespaces().Get(b.Namespace, metav1.GetOptions{}); apierrors.IsNotFound(err) { _, err = b.CoreClient.Namespaces().Create(&v1.Namespace{ObjectMeta: v1.ObjectMeta{Name: b.Namespace}}) if err != nil && !apierrors.IsAlreadyExists(err) { return nil, err } } sa, err = b.CoreClient.ServiceAccounts(b.Namespace).Create( &v1.ServiceAccount{ObjectMeta: v1.ObjectMeta{Namespace: b.Namespace, Name: name}}) if err != nil { return nil, err } } lw := &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { options.FieldSelector = fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(v1.SecretTypeServiceAccountToken)}).String() return b.CoreClient.Secrets(b.Namespace).List(options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { options.FieldSelector = fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(v1.SecretTypeServiceAccountToken)}).String() return b.CoreClient.Secrets(b.Namespace).Watch(options) }, } _, err = cache.ListWatchUntil(30*time.Second, lw, func(event watch.Event) (bool, error) { switch event.Type { case watch.Deleted: return false, nil case watch.Error: return false, fmt.Errorf("error watching") case watch.Added, watch.Modified: secret := event.Object.(*v1.Secret) if !serviceaccount.IsServiceAccountToken(secret, sa) || len(secret.Data[v1.ServiceAccountTokenKey]) == 0 { return false, nil } // TODO maybe verify the token is valid clientConfig.BearerToken = string(secret.Data[v1.ServiceAccountTokenKey]) restclient.AddUserAgent(clientConfig, serviceaccount.MakeUsername(b.Namespace, name)) return true, nil default: return false, fmt.Errorf("unexpected event type: %v", event.Type) } }) if err != nil { return nil, fmt.Errorf("unable to get token for service account: %v", err) } return clientConfig, nil }
func TestSAAsOAuthClient(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } authorizationCodes := make(chan string, 1) authorizationErrors := make(chan string, 1) oauthServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { t.Logf("fake pod server got %v", req.URL) if code := req.URL.Query().Get("code"); len(code) > 0 { authorizationCodes <- code } if err := req.URL.Query().Get("error"); len(err) > 0 { authorizationErrors <- err } })) defer oauthServer.Close() redirectURL := oauthServer.URL + "/oauthcallback" clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(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" if _, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projectName, "harold"); err != nil { t.Fatalf("unexpected error: %v", err) } if err := testserver.WaitForServiceAccounts(clusterAdminKubeClient, projectName, []string{"default"}); err != nil { t.Fatalf("unexpected error: %v", err) } promptingClient, err := clusterAdminClient.OAuthClients().Create(&oauthapi.OAuthClient{ ObjectMeta: kapi.ObjectMeta{Name: "prompting-client"}, Secret: "prompting-client-secret", RedirectURIs: []string{redirectURL}, GrantMethod: oauthapi.GrantHandlerPrompt, RespondWithChallenges: true, }) if err != nil { t.Fatalf("unexpected error: %v", err) } // get the SA ready with redirect URIs and secret annotations var defaultSA *kapi.ServiceAccount // retry this a couple times. We seem to be flaking on update conflicts and missing secrets all together err = kclient.RetryOnConflict(kclient.DefaultRetry, func() error { defaultSA, err = clusterAdminKubeClient.ServiceAccounts(projectName).Get("default") if err != nil { return err } if defaultSA.Annotations == nil { defaultSA.Annotations = map[string]string{} } defaultSA.Annotations[saoauth.OAuthRedirectURISecretAnnotationPrefix+"one"] = redirectURL defaultSA.Annotations[saoauth.OAuthWantChallengesAnnotationPrefix] = "true" defaultSA, err = clusterAdminKubeClient.ServiceAccounts(projectName).Update(defaultSA) return err }) if err != nil { t.Fatalf("unexpected error: %v", err) } var oauthSecret *kapi.Secret // retry this a couple times. We seem to be flaking on update conflicts and missing secrets all together err = wait.PollImmediate(30*time.Millisecond, 10*time.Second, func() (done bool, err error) { allSecrets, err := clusterAdminKubeClient.Secrets(projectName).List(kapi.ListOptions{}) if err != nil { return false, err } for i := range allSecrets.Items { secret := allSecrets.Items[i] if serviceaccount.IsServiceAccountToken(&secret, defaultSA) { oauthSecret = &secret return true, nil } } return false, nil }) if err != nil { t.Fatalf("unexpected error: %v", err) } // Test with a normal OAuth client { oauthClientConfig := &osincli.ClientConfig{ ClientId: promptingClient.Name, ClientSecret: promptingClient.Secret, AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize", TokenUrl: clusterAdminClientConfig.Host + "/oauth/token", RedirectUrl: redirectURL, SendClientSecretInParams: true, } t.Log("Testing unrestricted scope") oauthClientConfig.Scope = "" // approval steps are needed for unscoped access runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauth/approve", "form", "POST /oauth/approve", "redirect to /oauth/authorize", "redirect to /oauthcallback", "code", "scope:user:full", }) // verify the persisted client authorization looks like we expect if clientAuth, err := clusterAdminClient.OAuthClientAuthorizations().Get("harold:" + oauthClientConfig.ClientId); err != nil { t.Fatalf("Unexpected error: %v", err) } else if !reflect.DeepEqual(clientAuth.Scopes, []string{"user:full"}) { t.Fatalf("Unexpected scopes: %v", clientAuth.Scopes) } else { // update the authorization to not contain any approved scopes clientAuth.Scopes = nil if _, err := clusterAdminClient.OAuthClientAuthorizations().Update(clientAuth); err != nil { t.Fatalf("Unexpected error: %v", err) } } // approval steps are needed again for unscoped access runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauth/approve", "form", "POST /oauth/approve", "redirect to /oauth/authorize", "redirect to /oauthcallback", "code", "scope:user:full", }) // with the authorization stored, approval steps are skipped runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauthcallback", "code", "scope:user:full", }) // Approval step is needed again t.Log("Testing restricted scope") oauthClientConfig.Scope = "user:info user:check-access" // filter to disapprove of granting the user:check-access scope deniedScope := false inputFilter := func(inputType, name, value string) bool { if inputType == "checkbox" && name == "scope" && value == "user:check-access" { deniedScope = true return false } return true } // our token only gets the approved one runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, inputFilter, authorizationCodes, authorizationErrors, true, false, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauth/approve", "form", "POST /oauth/approve", "redirect to /oauth/authorize", "redirect to /oauthcallback", "code", "scope:user:info", }) if !deniedScope { t.Errorf("Expected form filter to deny user:info scope") } // second time, we approve all, and our token gets all requested scopes runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, false, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauth/approve", "form", "POST /oauth/approve", "redirect to /oauth/authorize", "redirect to /oauthcallback", "code", "scope:" + oauthClientConfig.Scope, }) // third time, the approval steps is not needed, and the token gets all requested scopes runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, false, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauthcallback", "code", "scope:" + oauthClientConfig.Scope, }) // Now request an unscoped token again, and no approval should be needed t.Log("Testing unrestricted scope") oauthClientConfig.Scope = "" runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauthcallback", "code", "scope:user:full", }) clusterAdminClient.OAuthClientAuthorizations().Delete("harold:" + oauthClientConfig.ClientId) } { oauthClientConfig := &osincli.ClientConfig{ ClientId: serviceaccount.MakeUsername(defaultSA.Namespace, defaultSA.Name), ClientSecret: string(oauthSecret.Data[kapi.ServiceAccountTokenKey]), AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize", TokenUrl: clusterAdminClientConfig.Host + "/oauth/token", RedirectUrl: redirectURL, Scope: scope.Join([]string{"user:info", "role:edit:" + projectName}), SendClientSecretInParams: true, } t.Log("Testing allowed scopes") // First time, the approval steps are needed // Second time, the approval steps are skipped runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauth/approve", "form", "POST /oauth/approve", "redirect to /oauth/authorize", "redirect to /oauthcallback", "code", "scope:" + oauthClientConfig.Scope, }) runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, true, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauthcallback", "code", "scope:" + oauthClientConfig.Scope, }) clusterAdminClient.OAuthClientAuthorizations().Delete("harold:" + oauthClientConfig.ClientId) } { oauthClientConfig := &osincli.ClientConfig{ ClientId: serviceaccount.MakeUsername(defaultSA.Namespace, defaultSA.Name), ClientSecret: string(oauthSecret.Data[kapi.ServiceAccountTokenKey]), AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize", TokenUrl: clusterAdminClientConfig.Host + "/oauth/token", RedirectUrl: redirectURL, Scope: scope.Join([]string{"user:info", "role:edit:other-ns"}), SendClientSecretInParams: true, } t.Log("Testing disallowed scopes") runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, false, false, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauthcallback", "error:access_denied", }) clusterAdminClient.OAuthClientAuthorizations().Delete("harold:" + oauthClientConfig.ClientId) } { t.Log("Testing invalid scopes") oauthClientConfig := &osincli.ClientConfig{ ClientId: serviceaccount.MakeUsername(defaultSA.Namespace, defaultSA.Name), ClientSecret: string(oauthSecret.Data[kapi.ServiceAccountTokenKey]), AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize", TokenUrl: clusterAdminClientConfig.Host + "/oauth/token", RedirectUrl: redirectURL, Scope: scope.Join([]string{"unknown-scope"}), SendClientSecretInParams: true, } runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, false, false, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauthcallback", "error:invalid_scope", }) clusterAdminClient.OAuthClientAuthorizations().Delete("harold:" + oauthClientConfig.ClientId) } { t.Log("Testing allowed scopes with failed API call") oauthClientConfig := &osincli.ClientConfig{ ClientId: serviceaccount.MakeUsername(defaultSA.Namespace, defaultSA.Name), ClientSecret: string(oauthSecret.Data[kapi.ServiceAccountTokenKey]), AuthorizeUrl: clusterAdminClientConfig.Host + "/oauth/authorize", TokenUrl: clusterAdminClientConfig.Host + "/oauth/token", RedirectUrl: redirectURL, Scope: scope.Join([]string{"user:info"}), SendClientSecretInParams: true, } // First time, the approval is needed // Second time, the approval is skipped runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, false, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauth/approve", "form", "POST /oauth/approve", "redirect to /oauth/authorize", "redirect to /oauthcallback", "code", "scope:" + oauthClientConfig.Scope, }) runOAuthFlow(t, clusterAdminClientConfig, projectName, oauthClientConfig, nil, authorizationCodes, authorizationErrors, true, false, []string{ "GET /oauth/authorize", "received challenge", "GET /oauth/authorize", "redirect to /oauthcallback", "code", "scope:" + oauthClientConfig.Scope, }) clusterAdminClient.OAuthClientAuthorizations().Delete("harold:" + oauthClientConfig.ClientId) } }
func TestLocalBindingOtherServiceAccountSubject(t *testing.T) { test := &authorizeTest{ context: kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), "adze"), &user.DefaultInfo{Name: serviceaccount.MakeUsername("other", "first")}), attributes: &DefaultAuthorizationAttributes{ Verb: "get", Resource: "pods", }, expectedAllowed: true, expectedReason: "allowed by rule in adze", } test.clusterPolicies = newDefaultClusterPolicies() test.policies = append(test.policies, newAdzePolicies()...) test.clusterBindings = newDefaultClusterPolicyBindings() test.bindings = append(test.bindings, newAdzeBindings()...) test.test(t) }
func TestClusterBindingServiceAccountSubject(t *testing.T) { test := &authorizeTest{ context: kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), kapi.NamespaceNone), &user.DefaultInfo{Name: serviceaccount.MakeUsername("foo", "default")}), attributes: &DefaultAuthorizationAttributes{ Verb: "get", Resource: "users", ResourceName: "any", }, expectedAllowed: true, expectedReason: "allowed by cluster rule", } test.clusterPolicies = newDefaultClusterPolicies() test.clusterBindings = newDefaultClusterPolicyBindings() test.test(t) }
// WithImpersonation is a filter that will inspect and check requests that attempt to change the user.Info for their requests func WithImpersonation(handler http.Handler, requestContextMapper api.RequestContextMapper, a authorizer.Authorizer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { impersonationRequests, err := buildImpersonationRequests(req.Header) if err != nil { glog.V(4).Infof("%v", err) internalError(w, req, err) return } if len(impersonationRequests) == 0 { handler.ServeHTTP(w, req) return } ctx, exists := requestContextMapper.Get(req) if !exists { internalError(w, req, errors.New("no context found for request")) return } requestor, exists := api.UserFrom(ctx) if !exists { internalError(w, req, errors.New("no user found for request")) return } // if groups are not specified, then we need to look them up differently depending on the type of user // if they are specified, then they are the authority groupsSpecified := len(req.Header[authenticationapi.ImpersonateGroupHeader]) > 0 // make sure we're allowed to impersonate each thing we're requesting. While we're iterating through, start building username // and group information username := "" groups := []string{} userExtra := map[string][]string{} for _, impersonationRequest := range impersonationRequests { actingAsAttributes := &authorizer.AttributesRecord{ User: requestor, Verb: "impersonate", APIGroup: impersonationRequest.GetObjectKind().GroupVersionKind().Group, Namespace: impersonationRequest.Namespace, Name: impersonationRequest.Name, ResourceRequest: true, } switch impersonationRequest.GetObjectKind().GroupVersionKind().GroupKind() { case api.Kind("ServiceAccount"): actingAsAttributes.Resource = "serviceaccounts" username = serviceaccount.MakeUsername(impersonationRequest.Namespace, impersonationRequest.Name) if !groupsSpecified { // if groups aren't specified for a service account, we know the groups because its a fixed mapping. Add them groups = serviceaccount.MakeGroupNames(impersonationRequest.Namespace, impersonationRequest.Name) } case api.Kind("User"): actingAsAttributes.Resource = "users" username = impersonationRequest.Name case api.Kind("Group"): actingAsAttributes.Resource = "groups" groups = append(groups, impersonationRequest.Name) case authenticationapi.Kind("UserExtra"): extraKey := impersonationRequest.FieldPath extraValue := impersonationRequest.Name actingAsAttributes.Resource = "userextras" actingAsAttributes.Subresource = extraKey userExtra[extraKey] = append(userExtra[extraKey], extraValue) default: glog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest) forbidden(actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest)) return } allowed, reason, err := a.Authorize(actingAsAttributes) if err != nil || !allowed { glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err) forbidden(actingAsAttributes, w, req, reason) return } } newUser := &user.DefaultInfo{ Name: username, Groups: groups, Extra: userExtra, } requestContextMapper.Update(req, api.WithUser(ctx, newUser)) oldUser, _ := api.UserFrom(ctx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) // clear all the impersonation headers from the request req.Header.Del(authenticationapi.ImpersonateUserHeader) req.Header.Del(authenticationapi.ImpersonateGroupHeader) for headerName := range req.Header { if strings.HasPrefix(headerName, authenticationapi.ImpersonateUserExtraHeaderPrefix) { req.Header.Del(headerName) } } handler.ServeHTTP(w, req) }) }
func TestServiceAccountAuthorization(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) saNamespace := api.NamespaceDefault saName := serviceaccountadmission.DefaultServiceAccountName saUsername := serviceaccount.MakeUsername(saNamespace, saName) // Start one OpenShift master as "cluster1" to play the external kube server cluster1MasterConfig, cluster1AdminConfigFile, err := testserver.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } cluster1AdminConfig, err := testutil.GetClusterAdminClientConfig(cluster1AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } cluster1AdminKubeClient, err := testutil.GetClusterAdminKubeClient(cluster1AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } cluster1AdminOSClient, err := testutil.GetClusterAdminClient(cluster1AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } // Get a service account token and build a client saToken, err := waitForServiceAccountToken(cluster1AdminKubeClient, saNamespace, saName, 20, time.Second) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(saToken) == 0 { t.Fatalf("token was not created") } cluster1SAClientConfig := restclient.Config{ Host: cluster1AdminConfig.Host, BearerToken: saToken, TLSClientConfig: restclient.TLSClientConfig{ CAFile: cluster1AdminConfig.CAFile, CAData: cluster1AdminConfig.CAData, }, } cluster1SAKubeClient, err := kclient.New(&cluster1SAClientConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Make sure the service account doesn't have access failNS := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-fail"}} if _, err := cluster1SAKubeClient.Namespaces().Create(failNS); !errors.IsForbidden(err) { t.Fatalf("expected forbidden error, got %v", err) } // Make the service account a cluster admin on cluster1 addRoleOptions := &policy.RoleModificationOptions{ RoleName: bootstrappolicy.ClusterAdminRoleName, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(cluster1AdminOSClient), Users: []string{saUsername}, } if err := addRoleOptions.AddRole(); err != nil { t.Fatalf("could not add role to service account") } // Give the policy cache a second to catch its breath time.Sleep(time.Second) // Make sure the service account now has access // This tests authentication using the etcd-based token getter passNS := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-pass"}} if _, err := cluster1SAKubeClient.Namespaces().Create(passNS); err != nil { t.Fatalf("unexpected error: %v", err) } // Create a kubeconfig from the serviceaccount config cluster1SAKubeConfigFile, err := ioutil.TempFile(testutil.GetBaseDir(), "cluster1-service-account.kubeconfig") if err != nil { t.Fatalf("error creating tmpfile: %v", err) } defer os.Remove(cluster1SAKubeConfigFile.Name()) if err := writeClientConfigToKubeConfig(cluster1SAClientConfig, cluster1SAKubeConfigFile.Name()); err != nil { t.Fatalf("error creating kubeconfig: %v", err) } // Set up cluster 2 to run against cluster 1 as external kubernetes cluster2MasterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("unexpected error: %v", err) } // Don't start kubernetes in process cluster2MasterConfig.KubernetesMasterConfig = nil // Connect to cluster1 using the service account credentials cluster2MasterConfig.MasterClients.ExternalKubernetesKubeConfig = cluster1SAKubeConfigFile.Name() // Don't start etcd cluster2MasterConfig.EtcdConfig = nil // Use the same credentials as cluster1 to connect to existing etcd cluster2MasterConfig.EtcdClientInfo = cluster1MasterConfig.EtcdClientInfo // Set a custom etcd prefix to make sure data is getting sent to cluster1 cluster2MasterConfig.EtcdStorageConfig.KubernetesStoragePrefix += "2" cluster2MasterConfig.EtcdStorageConfig.OpenShiftStoragePrefix += "2" // Don't manage any names in cluster2 cluster2MasterConfig.ServiceAccountConfig.ManagedNames = []string{} // Don't create any service account tokens in cluster2 cluster2MasterConfig.ServiceAccountConfig.PrivateKeyFile = "" // Use the same public keys to validate tokens as cluster1 cluster2MasterConfig.ServiceAccountConfig.PublicKeyFiles = cluster1MasterConfig.ServiceAccountConfig.PublicKeyFiles // don't try to start second dns server cluster2MasterConfig.DNSConfig = nil // Start cluster 2 (without clearing etcd) and get admin client configs and clients cluster2Options := testserver.TestOptions{EnableControllers: true} cluster2AdminConfigFile, err := testserver.StartConfiguredMasterWithOptions(cluster2MasterConfig, cluster2Options) if err != nil { t.Fatalf("unexpected error: %v", err) } cluster2AdminConfig, err := testutil.GetClusterAdminClientConfig(cluster2AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } cluster2AdminOSClient, err := testutil.GetClusterAdminClient(cluster2AdminConfigFile) if err != nil { t.Fatalf("unexpected error: %v", err) } // Build a client to use the same service account token against cluster2 cluster2SAClientConfig := cluster1SAClientConfig cluster2SAClientConfig.Host = cluster2AdminConfig.Host cluster2SAKubeClient, err := kclient.New(&cluster2SAClientConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } // Make sure the service account doesn't have access // A forbidden error makes sure the token was recognized, and policy denied us // This exercises the client-based token getter // It also makes sure we don't loop back through the cluster2 kube proxy which would cause an auth loop failNS2 := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-fail2"}} if _, err := cluster2SAKubeClient.Namespaces().Create(failNS2); !errors.IsForbidden(err) { t.Fatalf("expected forbidden error, got %v", err) } // Make the service account a cluster admin on cluster2 addRoleOptions2 := &policy.RoleModificationOptions{ RoleName: bootstrappolicy.ClusterAdminRoleName, RoleBindingAccessor: policy.NewClusterRoleBindingAccessor(cluster2AdminOSClient), Users: []string{saUsername}, } if err := addRoleOptions2.AddRole(); err != nil { t.Fatalf("could not add role to service account") } // Give the policy cache a second to catch its breath time.Sleep(time.Second) // Make sure the service account now has access to cluster2 passNS2 := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: "test-pass2"}} if _, err := cluster2SAKubeClient.Namespaces().Create(passNS2); err != nil { t.Fatalf("unexpected error: %v", err) } // Make sure the ns actually got created in cluster1 if _, err := cluster1SAKubeClient.Namespaces().Get(passNS2.Name); err != nil { t.Fatalf("unexpected error: %v", err) } }