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()
}