pkg/cmd/serviceaccount/create.go (165 lines of code) (raw):

package serviceaccount import ( "context" "fmt" "time" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/spf13/cobra" "monis.app/mlog" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/Azure/azure-workload-identity/pkg/cloud" "github.com/Azure/azure-workload-identity/pkg/cmd/serviceaccount/auth" "github.com/Azure/azure-workload-identity/pkg/cmd/serviceaccount/options" phases "github.com/Azure/azure-workload-identity/pkg/cmd/serviceaccount/phases/create" "github.com/Azure/azure-workload-identity/pkg/cmd/serviceaccount/phases/workflow" "github.com/Azure/azure-workload-identity/pkg/cmd/serviceaccount/util" "github.com/Azure/azure-workload-identity/pkg/kuberneteshelper" "github.com/Azure/azure-workload-identity/pkg/webhook" ) func newCreateCmd(authProvider auth.Provider) *cobra.Command { createRunner := workflow.NewPhaseRunner() data := &createData{ authProvider: authProvider, } cmd := &cobra.Command{ Use: "create", RunE: func(cmd *cobra.Command, args []string) error { return createRunner.Run(data) }, } f := cmd.Flags() f.StringVar(&data.serviceAccountName, options.ServiceAccountName.Flag, "", options.ServiceAccountName.Description) f.StringVar(&data.serviceAccountNamespace, options.ServiceAccountNamespace.Flag, "default", options.ServiceAccountNamespace.Description) f.StringVar(&data.serviceAccountIssuerURL, options.ServiceAccountIssuerURL.Flag, "", options.ServiceAccountIssuerURL.Description) f.DurationVar(&data.serviceAccountTokenExpiration, options.ServiceAccountTokenExpiration.Flag, time.Duration(webhook.DefaultServiceAccountTokenExpiration)*time.Second, options.ServiceAccountTokenExpiration.Description) f.StringVar(&data.aadApplicationName, options.AADApplicationName.Flag, "", options.AADApplicationName.Description) f.StringVar(&data.aadApplicationClientID, options.AADApplicationClientID.Flag, "", options.AADApplicationClientID.Description) f.StringVar(&data.aadApplicationObjectID, options.AADApplicationObjectID.Flag, "", options.AADApplicationObjectID.Description) f.StringVar(&data.servicePrincipalName, options.ServicePrincipalName.Flag, "", options.ServicePrincipalName.Description) f.StringVar(&data.servicePrincipalObjectID, options.ServicePrincipalObjectID.Flag, "", options.ServicePrincipalObjectID.Description) f.StringVar(&data.azureScope, options.AzureScope.Flag, "", options.AzureScope.Description) f.StringVar(&data.azureRole, options.AzureRole.Flag, "", options.AzureRole.Description) // append phases in order createRunner.AppendPhases( phases.NewAADApplicationPhase(), phases.NewServiceAccountPhase(), phases.NewFederatedIdentityPhase(), phases.NewRoleAssignmentPhase(), ) createRunner.BindToCommand(cmd, data) return cmd } // createData is an implementation of phases.CreateData in // pkg/cmd/serviceaccount/phases/create/data.go type createData struct { serviceAccountName string serviceAccountNamespace string serviceAccountIssuerURL string serviceAccountTokenExpiration time.Duration aadApplication models.Applicationable // cache aadApplicationName string aadApplicationClientID string aadApplicationObjectID string servicePrincipal models.ServicePrincipalable // cache servicePrincipalObjectID string servicePrincipalName string azureRole string azureScope string authProvider auth.Provider } var _ phases.CreateData = &createData{} // ServiceAccountName returns the name of the service account. func (c *createData) ServiceAccountName() string { return c.serviceAccountName } // ServiceAccountNamespace returns the namespace of the service account. func (c *createData) ServiceAccountNamespace() string { return c.serviceAccountNamespace } // ServiceAccountIssuerURL returns the issuer URL of the service account. func (c *createData) ServiceAccountIssuerURL() string { return c.serviceAccountIssuerURL } // ServiceAccountTokenExpiration returns the expiration time of the service account token. func (c *createData) ServiceAccountTokenExpiration() time.Duration { return c.serviceAccountTokenExpiration } // AADApplication returns the AAD application object. // This will return the cached value if it has been created. func (c *createData) AADApplication() (models.Applicationable, error) { if c.aadApplication == nil { app, err := c.AzureClient().GetApplication(context.Background(), c.AADApplicationName()) if err != nil { return nil, err } c.aadApplication = app } return c.aadApplication, nil } // AADApplicationName returns the name of the AAD application. func (c *createData) AADApplicationName() string { name := c.aadApplicationName if name == "" { if c.ServiceAccountNamespace() != "" && c.ServiceAccountName() != "" && c.ServiceAccountIssuerURL() != "" { mlog.Warning("--aad-application-name not specified, constructing name with service account namespace, name, and the hash of the issuer URL") name = fmt.Sprintf("%s-%s-%s", c.ServiceAccountNamespace(), c.serviceAccountName, util.GetIssuerHash(c.ServiceAccountIssuerURL())) } } return name } // AADApplicationClientID returns the client ID of the AAD application. // This will be used for annotating the service account. func (c *createData) AADApplicationClientID() string { if c.aadApplicationClientID != "" { return c.aadApplicationClientID } app, err := c.AADApplication() if err != nil { mlog.Error("failed to get AAD application client ID. Returning an empty string", err) return "" } return *app.GetAppId() } // AADApplicationObjectID returns the object ID of the AAD application. // This will be used for creating or removing the federated identity credential. func (c *createData) AADApplicationObjectID() string { if c.aadApplicationObjectID != "" { return c.aadApplicationObjectID } app, err := c.AADApplication() if err != nil { mlog.Error("failed to get AAD application object ID. Returning an empty string", err) return "" } return *app.GetId() } // ServicePrincipal returns the service principal object. // This will return the cached value if it has been created. func (c *createData) ServicePrincipal() (models.ServicePrincipalable, error) { if c.servicePrincipal == nil { sp, err := c.AzureClient().GetServicePrincipal(context.Background(), c.ServicePrincipalName()) if err != nil { return nil, err } c.servicePrincipal = sp } return c.servicePrincipal, nil } // ServicePrincipalName returns the name of the service principal. func (c *createData) ServicePrincipalName() string { name := c.servicePrincipalName // fall back to the name of the AAD application if name == "" { mlog.Warning("--service-principal-name not specified, falling back to AAD application name") name = c.AADApplicationName() } return name } // ServicePrincipalObjectID returns the object ID of the service principal. // This will be used for creating or removing the role assignment. func (c *createData) ServicePrincipalObjectID() string { if c.servicePrincipalObjectID != "" { return c.servicePrincipalObjectID } sp, err := c.ServicePrincipal() if err != nil { mlog.Error("failed to get service principal object ID. Returning an empty string", err) return "" } return *sp.GetId() } // AzureRole returns the Azure role. func (c *createData) AzureRole() string { return c.azureRole } // AzureScope returns the Azure scope. func (c *createData) AzureScope() string { return c.azureScope } // AzureTenantID returns the Azure tenant ID. func (c *createData) AzureTenantID() string { return c.authProvider.GetAzureTenantID() } // AzureClient returns the Azure client. func (c *createData) AzureClient() cloud.Interface { return c.authProvider.GetAzureClient() } // KubeClient returns the Kubernetes client. func (c *createData) KubeClient() (client.Client, error) { return kuberneteshelper.GetKubeClient() }