// TODO: when Secrets in kapi.ServiceAccount get changed to MountSecrets and represented by LocalObjectReferences, this can be // refactored to reuse the addition code better // addSecretsToServiceAccount adds secrets to the service account, either as pull secrets, mount secrets, or both. func (o AddSecretOptions) addSecretsToServiceAccount(serviceaccount *kapi.ServiceAccount) error { updated := false newSecrets, err := o.getSecrets() if err != nil { return err } newSecretNames := getSecretNames(newSecrets) if o.ForMount { currentSecrets := getMountSecretNames(serviceaccount) secretsToAdd := newSecretNames.Difference(currentSecrets) for _, secretName := range secretsToAdd.List() { serviceaccount.Secrets = append(serviceaccount.Secrets, kapi.ObjectReference{Name: secretName}) updated = true } } if o.ForPull { currentSecrets := getPullSecretNames(serviceaccount) secretsToAdd := newSecretNames.Difference(currentSecrets) for _, secretName := range secretsToAdd.List() { serviceaccount.ImagePullSecrets = append(serviceaccount.ImagePullSecrets, kapi.LocalObjectReference{Name: secretName}) updated = true } } if updated { _, err = o.ClientInterface.ServiceAccounts(o.Namespace).Update(serviceaccount) return err } return nil }
// TODO: when Secrets in kapi.ServiceAccount get changed to MountSecrets and represented by LocalObjectReferences, this can be // refactored to reuse the addition code better // linkSecretsToServiceAccount links secrets to the service account, either as pull secrets, mount secrets, or both. func (o LinkSecretOptions) linkSecretsToServiceAccount(serviceaccount *kapi.ServiceAccount) error { updated := false newSecrets, failLater, err := o.GetSecrets() if err != nil { return err } newSecretNames := o.GetSecretNames(newSecrets) if o.ForMount { currentSecrets := o.GetMountSecretNames(serviceaccount) secretsToLink := newSecretNames.Difference(currentSecrets) for _, secretName := range secretsToLink.List() { serviceaccount.Secrets = append(serviceaccount.Secrets, kapi.ObjectReference{Name: secretName}) updated = true } } if o.ForPull { currentSecrets := o.GetPullSecretNames(serviceaccount) secretsToLink := newSecretNames.Difference(currentSecrets) for _, secretName := range secretsToLink.List() { serviceaccount.ImagePullSecrets = append(serviceaccount.ImagePullSecrets, kapi.LocalObjectReference{Name: secretName}) updated = true } } if updated { _, err = o.ClientInterface.ServiceAccounts(o.Namespace).Update(serviceaccount) return err } if failLater { return errors.New("Some secrets could not be linked") } return nil }
// unlinkSecretsFromServiceAccount detaches pull and mount secrets from the service account. func (o UnlinkSecretOptions) unlinkSecretsFromServiceAccount(serviceaccount *kapi.ServiceAccount) error { // All of the requested secrets must be present in either the Mount or Pull secrets // If any of them are not present, we'll return an error and push no changes. rmSecrets, failLater, err := o.GetSecrets() if err != nil { return err } rmSecretNames := o.GetSecretNames(rmSecrets) newMountSecrets := []kapi.ObjectReference{} newPullSecrets := []kapi.LocalObjectReference{} // Check the mount secrets for i := len(serviceaccount.Secrets) - 1; i >= 0; i-- { found := false for _, secretname := range rmSecretNames.List() { if secretname == serviceaccount.Secrets[i].Name { found = true // Skip adding this to the updated list } } if !found { // Copy this back in, since it doesn't match the ones we're removing newMountSecrets = append(newMountSecrets, serviceaccount.Secrets[i]) } } // Check the image pull secrets for i := len(serviceaccount.ImagePullSecrets) - 1; i >= 0; i-- { found := false for _, secretname := range rmSecretNames.List() { if secretname == serviceaccount.ImagePullSecrets[i].Name { found = true // Skip adding this to the updated list } } if !found { // Copy this back in, since it doesn't match the one we're removing newPullSecrets = append(newPullSecrets, serviceaccount.ImagePullSecrets[i]) } } // Save the updated Secret lists back to the server serviceaccount.Secrets = newMountSecrets serviceaccount.ImagePullSecrets = newPullSecrets _, err = o.ClientInterface.ServiceAccounts(o.Namespace).Update(serviceaccount) if err != nil { return err } if failLater { return errors.New("Some secrets could not be unlinked") } return nil }
// unlinkSecretsFromServiceAccount detaches pull and mount secrets from the service account. func (o UnlinkSecretOptions) unlinkSecretsFromServiceAccount(serviceaccount *kapi.ServiceAccount) error { // All of the requested secrets must be present in either the Mount or Pull secrets // If any of them are not present, we'll return an error and push no changes. rmSecrets, hasNotFound, err := o.GetSecrets(true) if err != nil { return err } rmSecretNames := o.GetSecretNames(rmSecrets) newMountSecrets := []kapi.ObjectReference{} newPullSecrets := []kapi.LocalObjectReference{} updated := false // Check the mount secrets for _, secret := range serviceaccount.Secrets { if !rmSecretNames.Has(secret.Name) { // Copy this back in, since it doesn't match the ones we're removing newMountSecrets = append(newMountSecrets, secret) } else { updated = true } } // Check the image pull secrets for _, imagePullSecret := range serviceaccount.ImagePullSecrets { if !rmSecretNames.Has(imagePullSecret.Name) { // Copy this back in, since it doesn't match the one we're removing newPullSecrets = append(newPullSecrets, imagePullSecret) } else { updated = true } } if updated { // Save the updated Secret lists back to the server serviceaccount.Secrets = newMountSecrets serviceaccount.ImagePullSecrets = newPullSecrets _, err = o.ClientInterface.ServiceAccounts(o.Namespace).Update(serviceaccount) if err != nil { return err } if hasNotFound { return fmt.Errorf("Unlinked deleted secrets from %s/%s service account", o.Namespace, serviceaccount.Name) } return nil } else { return errors.New("No valid secrets found or secrets not linked to service account") } }
// removeSecretReferenceIfNeeded updates the given ServiceAccount to remove a reference to the given secretName if needed. // Returns whether an update was performed, and any error that occurred func (e *TokensController) removeSecretReferenceIfNeeded(serviceAccount *api.ServiceAccount, secretName string) (bool, error) { // See if the account even referenced the secret if !getSecretReferences(serviceAccount).Has(secretName) { return false, nil } // We don't want to update the cache's copy of the service account // so remove the secret from a freshly retrieved copy of the service account serviceAccounts := e.client.ServiceAccounts(serviceAccount.Namespace) serviceAccount, err := serviceAccounts.Get(serviceAccount.Name) if err != nil { return false, err } // Double-check to see if the account still references the secret if !getSecretReferences(serviceAccount).Has(secretName) { return false, nil } secrets := []api.ObjectReference{} for _, s := range serviceAccount.Secrets { if s.Name != secretName { secrets = append(secrets, s) } } serviceAccount.Secrets = secrets _, err = serviceAccounts.Update(serviceAccount) if err != nil { return false, err } return true, nil }
// RunServiceAccountsController starts the service account controller func (c *MasterConfig) RunServiceAccountsController() { if len(c.Options.ServiceAccountConfig.ManagedNames) == 0 { glog.Infof("Skipped starting Service Account Manager, no managed names specified") return } options := sacontroller.DefaultServiceAccountsControllerOptions() options.ServiceAccounts = []kapi.ServiceAccount{} for _, saName := range c.Options.ServiceAccountConfig.ManagedNames { sa := kapi.ServiceAccount{} sa.Name = saName options.ServiceAccounts = append(options.ServiceAccounts, sa) } sacontroller.NewServiceAccountsController(clientadapter.FromUnversionedClient(c.KubeClient()), options).Run() }
// createTokenSecret creates a token secret for a given service account. Returns the name of the token func (e *DockercfgController) createTokenSecret(serviceAccount *api.ServiceAccount) (*api.Secret, bool, error) { pendingTokenName := serviceAccount.Annotations[PendingTokenAnnotation] // If this service account has no record of a pending token name, record one if len(pendingTokenName) == 0 { pendingTokenName = secret.Strategy.GenerateName(osautil.GetTokenSecretNamePrefix(serviceAccount)) if serviceAccount.Annotations == nil { serviceAccount.Annotations = map[string]string{} } serviceAccount.Annotations[PendingTokenAnnotation] = pendingTokenName updatedServiceAccount, err := e.client.Core().ServiceAccounts(serviceAccount.Namespace).Update(serviceAccount) // Conflicts mean we'll get called to sync this service account again if kapierrors.IsConflict(err) { return nil, false, nil } if err != nil { return nil, false, err } serviceAccount = updatedServiceAccount } // Return the token from cache existingTokenSecretObj, exists, err := e.secretCache.GetByKey(serviceAccount.Namespace + "/" + pendingTokenName) if err != nil { return nil, false, err } if exists { existingTokenSecret := existingTokenSecretObj.(*api.Secret) return existingTokenSecret, len(existingTokenSecret.Data[api.ServiceAccountTokenKey]) > 0, nil } // Try to create the named pending token tokenSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: pendingTokenName, Namespace: serviceAccount.Namespace, Annotations: map[string]string{ api.ServiceAccountNameKey: serviceAccount.Name, api.ServiceAccountUIDKey: string(serviceAccount.UID), api.CreatedByAnnotation: CreateDockercfgSecretsController, }, }, Type: api.SecretTypeServiceAccountToken, Data: map[string][]byte{}, } glog.V(4).Infof("Creating token secret %q for service account %s/%s", tokenSecret.Name, serviceAccount.Namespace, serviceAccount.Name) token, err := e.client.Core().Secrets(tokenSecret.Namespace).Create(tokenSecret) // Already exists but not in cache means we'll get an add watch event and resync if kapierrors.IsAlreadyExists(err) { return nil, false, nil } if err != nil { return nil, false, err } return token, len(token.Data[api.ServiceAccountTokenKey]) > 0, nil }
// createSecret creates a secret of type ServiceAccountToken for the given ServiceAccount func (e *TokensController) createSecret(serviceAccount *api.ServiceAccount) error { // Build the secret secret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: secret.Strategy.GenerateName(fmt.Sprintf("%s-token-", serviceAccount.Name)), Namespace: serviceAccount.Namespace, Annotations: map[string]string{ api.ServiceAccountNameKey: serviceAccount.Name, api.ServiceAccountUIDKey: string(serviceAccount.UID), }, }, Type: api.SecretTypeServiceAccountToken, Data: map[string][]byte{}, } // Generate the token token, err := e.token.GenerateToken(*serviceAccount, *secret) if err != nil { return err } secret.Data[api.ServiceAccountTokenKey] = []byte(token) if e.rootCA != nil && len(e.rootCA) > 0 { secret.Data[api.ServiceAccountRootCAKey] = e.rootCA } // Save the secret if _, err := e.client.Secrets(serviceAccount.Namespace).Create(secret); err != nil { return err } // We don't want to update the cache's copy of the service account // so add the secret to a freshly retrieved copy of the service account serviceAccounts := e.client.ServiceAccounts(serviceAccount.Namespace) serviceAccount, err = serviceAccounts.Get(serviceAccount.Name) if err != nil { return err } serviceAccount.Secrets = append(serviceAccount.Secrets, api.ObjectReference{Name: secret.Name}) _, err = serviceAccounts.Update(serviceAccount) if err != nil { // we weren't able to use the token, try to clean it up. glog.V(2).Infof("Deleting secret %s/%s because reference couldn't be added (%v)", secret.Namespace, secret.Name, err) if err := e.client.Secrets(secret.Namespace).Delete(secret.Name); err != nil { glog.Error(err) // if we fail, just log it } } if apierrors.IsConflict(err) { // nothing to do. We got a conflict, that means that the service account was updated. We simply need to return because we'll get an update notification later return nil } return err }
// createDefaultServiceAccount creates a default ServiceAccount in the specified namespace func (e *ServiceAccountsController) createServiceAccount(sa api.ServiceAccount, namespace string) { sa.Namespace = namespace if _, err := e.client.Core().ServiceAccounts(namespace).Create(&sa); err != nil && !apierrs.IsAlreadyExists(err) { glog.Error(err) } }
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 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) } }
// createDefaultServiceAccount creates a default ServiceAccount in the specified namespace func (e *ServiceAccountsController) createServiceAccount(sa api.ServiceAccount, namespace string) { sa.Namespace = namespace if _, err := e.client.ServiceAccounts(namespace).Create(&sa); err != nil { glog.Error(err) } }