Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for workload identity #2619

Merged
merged 16 commits into from
May 15, 2024
19 changes: 4 additions & 15 deletions cmd/credentialUtil.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,35 +104,24 @@ func GetOAuthTokenManagerInstance() (*common.UserOAuthTokenManager, error) {
}

// Fill up lca
lca.loginType = autoLoginType
switch autoLoginType {
case common.AutologinTypeSPN:
lca.applicationID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ApplicationID())
lca.certPath = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.CertificatePath())
lca.certPass = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.CertificatePassword())
lca.clientSecret = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ClientSecret())
lca.servicePrincipal = true

case common.AutologinTypeMSI:
lca.identityClientID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ManagedIdentityClientID())
lca.identityObjectID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ManagedIdentityObjectID())
lca.identityResourceID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ManagedIdentityResourceString())
lca.identity = true

case common.AutologinTypeDevice:
lca.identity = false

case common.AutologinTypeAzCLI:
lca.identity = false
lca.servicePrincipal = false
lca.psCred = false
lca.azCliCred = true

case common.AutologinTypePsCred:
lca.identity = false
lca.servicePrincipal = false
lca.azCliCred = false
lca.psCred = true

case common.AutologinTypeWorkload:
lca.identityClientID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.WorkloadIdentityClientID())
lca.tokenFilePath = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.WorkloadIdentityTokenFilePath())
default:
glcm.Error("Invalid Auto-login type specified: " + autoLoginType)
return
Expand Down
96 changes: 42 additions & 54 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
glcm.Info(environmentVariableNotice)
}

loginCmdArg.loginType = strings.ToLower(loginCmdArg.loginType)

err := loginCmdArg.process()
if err != nil {
// the errors from adal contains \r\n in the body, get rid of them to make the error easier to look at
Expand All @@ -65,19 +67,26 @@

lgCmd.PersistentFlags().StringVar(&loginCmdArg.tenantID, "tenant-id", "", "The Azure Active Directory tenant ID to use for OAuth device interactive login.")
lgCmd.PersistentFlags().StringVar(&loginCmdArg.aadEndpoint, "aad-endpoint", "", "The Azure Active Directory endpoint to use. The default ("+common.DefaultActiveDirectoryEndpoint+") is correct for the public Azure cloud. Set this parameter when authenticating in a national cloud. Not needed for Managed Service Identity")
// Use identity which aligns to Azure powershell and CLI.

lgCmd.PersistentFlags().BoolVar(&loginCmdArg.identity, "identity", false, "Log in using virtual machine's identity, also known as managed service identity (MSI).")
// Use SPN certificate to log in.
lgCmd.PersistentFlags().BoolVar(&loginCmdArg.servicePrincipal, "service-principal", false, "Log in via Service Principal Name (SPN) by using a certificate or a secret. The client secret or certificate password must be placed in the appropriate environment variable. Type AzCopy env to see names and descriptions of environment variables.")
// Client ID of user-assigned identity.
lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityClientID, "identity-client-id", "", "Client ID of user-assigned identity.")
// Resource ID of user-assigned identity.
lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityResourceID, "identity-resource-id", "", "Resource ID of user-assigned identity.")
// Deprecate these flags in favor of a new login type flag
_ = lgCmd.PersistentFlags().MarkHidden("identity")
_ = lgCmd.PersistentFlags().MarkHidden("service-principal")

lgCmd.PersistentFlags().StringVar(&loginCmdArg.loginType, "login-type", common.AutologinTypeDevice, "Default value is DEVICE. Specify the credential type to access Azure Resource, available values SPN, MSI, DEVICE, AZCLI, and PSCRED, WORKLOAD - sequentially for Service Principal, Managed Service Identity, Device workflow, Azure CLI, Azure PowerShell. or Workload Identity.")

//login with SPN
// SPN flags
lgCmd.PersistentFlags().StringVar(&loginCmdArg.applicationID, "application-id", "", "Application ID of user-assigned identity. Required for service principal auth.")
lgCmd.PersistentFlags().StringVar(&loginCmdArg.certPath, "certificate-path", "", "Path to certificate for SPN authentication. Required for certificate-based service principal auth.")

// Managed Identity flags
lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityResourceID, "identity-resource-id", "", "Resource ID of user-assigned identity.")

lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityClientID, "identity-client-id", "", "Client ID of user-assigned or federated identity.")
// Workload Identity flags
lgCmd.PersistentFlags().StringVar(&loginCmdArg.tokenFilePath, "token-file-path", "", "The path of a file containing a Kubernetes service account token.")

// Deprecate the identity-object-id flag
_ = lgCmd.PersistentFlags().MarkHidden("identity-object-id") // Object ID of user-assigned identity.
lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityObjectID, "identity-object-id", "", "Object ID of user-assigned identity. This parameter is deprecated. Please use client id or resource id")
Expand All @@ -91,8 +100,8 @@

identity bool // Whether to use MSI.
servicePrincipal bool
azCliCred bool
psCred bool

loginType string

// Info of VM's user assigned identity, client or object ids of the service identity are required if
// your VM has multiple user-assigned managed identities.
Expand All @@ -101,6 +110,9 @@
identityObjectID string
identityResourceID string

// Workload Identity flags
tokenFilePath string

//Required to sign in with a SPN (Service Principal Name)
applicationID string
certPath string
Expand All @@ -110,45 +122,19 @@
}

func (lca loginCmdArgs) validate() error {
gapra-msft marked this conversation as resolved.
Show resolved Hide resolved
// Only support one kind of oauth login at same time.
switch {
case lca.identity:
if lca.servicePrincipal {
return errors.New("you can only log in with one type of auth at once")
}

// Consider only command-line parameters as env vars are a hassle to change and it's not like we'll use them here.
if lca.tenantID != "" || lca.applicationID != "" || lca.certPath != "" {
return errors.New("tenant ID/application ID/cert path/client secret cannot be used with identity")
}
case lca.servicePrincipal:
if lca.identity {
return errors.New("you can only log in with one type of auth at once")
}

if lca.identityClientID != "" || lca.identityObjectID != "" || lca.identityResourceID != "" {
return errors.New("identity client/object/resource ID are exclusive to managed service identity auth and are not compatible with service principal auth")
}

if lca.applicationID == "" || (lca.clientSecret == "" && lca.certPath == "") {
return errors.New("service principal auth requires an application ID, and client secret/certificate")
}
default: // OAuth login.
// Login type consolidation to allow backward compatibility.
if lca.servicePrincipal || lca.identity {
glcm.Warn("The flags --service-principal and --identity will be deprecated in a future release. Please use --login-type=SPN or --login-type=MSI instead.")
}
if lca.servicePrincipal {
lca.loginType = common.AutologinTypeSPN

Check failure on line 130 in cmd/login.go

View workflow job for this annotation

GitHub Actions / lint (1.19, ubuntu-latest)

SA4005: ineffective assignment to field loginCmdArgs.loginType (staticcheck)

Check failure on line 130 in cmd/login.go

View workflow job for this annotation

GitHub Actions / lint (1.19, windows-latest)

SA4005: ineffective assignment to field loginCmdArgs.loginType (staticcheck)

Check failure on line 130 in cmd/login.go

View workflow job for this annotation

GitHub Actions / lint (1.19, macos-latest)

SA4005: ineffective assignment to field loginCmdArgs.loginType (staticcheck)
} else if lca.identity {
lca.loginType = common.AutologinTypeMSI

Check failure on line 132 in cmd/login.go

View workflow job for this annotation

GitHub Actions / lint (1.19, ubuntu-latest)

SA4005: ineffective assignment to field loginCmdArgs.loginType (staticcheck)

Check failure on line 132 in cmd/login.go

View workflow job for this annotation

GitHub Actions / lint (1.19, windows-latest)

SA4005: ineffective assignment to field loginCmdArgs.loginType (staticcheck)

Check failure on line 132 in cmd/login.go

View workflow job for this annotation

GitHub Actions / lint (1.19, macos-latest)

SA4005: ineffective assignment to field loginCmdArgs.loginType (staticcheck)
} else if lca.servicePrincipal && lca.identity {
// This isn't necessary, but stands as a sanity check. It will never be hit.
if lca.servicePrincipal || lca.identity {
return errors.New("you can only log in with one type of auth at once")
}

// Consider only command-line parameters as env vars are a hassle to change and it's not like we'll use them here.
if lca.applicationID != "" || lca.certPath != "" {
return errors.New("application ID and certificate paths are exclusive to service principal auth and are not compatible with OAuth")
}

if lca.identityClientID != "" || lca.identityObjectID != "" || lca.identityResourceID != "" {
return errors.New("identity client/object/resource IDs are exclusive to managed service identity auth and are not compatible with OAuth")
}
return errors.New("you can only log in with one type of auth at once")
}

// Any required variables for login type will be validated by the Azure Identity SDK.
return nil
}

Expand All @@ -161,23 +147,20 @@
uotm := GetUserOAuthTokenManagerInstance()
// Persist the token to cache, if login fulfilled successfully.

switch {
case lca.servicePrincipal:

switch lca.loginType {
case common.AutologinTypeSPN:
if lca.certPath != "" {
if err := uotm.CertLogin(lca.tenantID, lca.aadEndpoint, lca.certPath, lca.certPass, lca.applicationID, lca.persistToken); err != nil {
return err
}

glcm.Info("SPN Auth via cert succeeded.")
} else {
if err := uotm.SecretLogin(lca.tenantID, lca.aadEndpoint, lca.clientSecret, lca.applicationID, lca.persistToken); err != nil {
return err
}

glcm.Info("SPN Auth via secret succeeded.")
}
case lca.identity:
case common.AutologinTypeMSI:
if err := uotm.MSILogin(common.IdentityInfo{
ClientID: lca.identityClientID,
ObjectID: lca.identityObjectID,
Expand All @@ -187,16 +170,21 @@
}
// For MSI login, info success message to user.
glcm.Info("Login with identity succeeded.")
case lca.azCliCred:
case common.AutologinTypeAzCLI:
if err := uotm.AzCliLogin(lca.tenantID); err != nil {
return err
}
glcm.Info("Login with AzCliCreds succeeded")
case lca.psCred:
case common.AutologinTypePsCred:
if err := uotm.PSContextToken(lca.tenantID); err != nil {
return err
}
glcm.Info("Login with Powershell context succeeded")
case common.AutologinTypeWorkload:
if err := uotm.WorkloadIdentityLogin(lca.tenantID, lca.identityClientID, lca.tokenFilePath, lca.persistToken); err != nil {
return err
}
glcm.Info("Login with Workload Identity succeeded")
default:
if err := uotm.UserLogin(lca.tenantID, lca.aadEndpoint, lca.persistToken); err != nil {
return err
Expand Down
27 changes: 21 additions & 6 deletions common/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,18 @@ func (EnvironmentVariable) UserDir() EnvironmentVariable {
}

const (
AutologinTypeSPN = "spn"
AutologinTypeMSI = "msi"
AutologinTypeDevice = "device"
AutologinTypeAzCLI = "azcli"
AutologinTypePsCred = "pscred"
AutologinTypeSPN = "spn"
AutologinTypeMSI = "msi"
AutologinTypeDevice = "device"
AutologinTypeAzCLI = "azcli"
AutologinTypePsCred = "pscred"
AutologinTypeWorkload = "workload"
)

func (EnvironmentVariable) AutoLoginType() EnvironmentVariable {
return EnvironmentVariable{
Name: "AZCOPY_AUTO_LOGIN_TYPE",
Description: "Specify the credential type to access Azure Resource without invoking the login command and using the OS secret store, available values SPN, MSI, DEVICE, AZCLI, and PSCRED - sequentially for Service Principal, Managed Service Identity, Device workflow, Azure CLI, or Azure PowerShell.",
Description: "Specify the credential type to access Azure Resource without invoking the login command and using the OS secret store, available values SPN, MSI, DEVICE, AZCLI, and PSCRED, WORKLOAD - sequentially for Service Principal, Managed Service Identity, Device workflow, Azure CLI, Azure PowerShell. or Workload Identity.",
}
}

Expand Down Expand Up @@ -164,6 +165,20 @@ func (EnvironmentVariable) ManagedIdentityResourceString() EnvironmentVariable {
}
}

func (EnvironmentVariable) WorkloadIdentityClientID() EnvironmentVariable {
return EnvironmentVariable{
Name: "AZCOPY_WORKLOAD_CLIENT_ID",
Description: "Client ID for Workload identity. This variable is only used for auto login, please use the command line flag instead when invoking the login command.",
}
}

func (EnvironmentVariable) WorkloadIdentityTokenFilePath() EnvironmentVariable {
return EnvironmentVariable{
Name: "AZCOPY_WORKLOAD_TOKEN_FILE_PATH",
Description: "Token file path for Workload identity. This variable is only used for auto login, please use the command line flag instead when invoking the login command.",
}
}

gapra-msft marked this conversation as resolved.
Show resolved Hide resolved
func (EnvironmentVariable) ConcurrencyValue() EnvironmentVariable {
return EnvironmentVariable{
Name: "AZCOPY_CONCURRENCY_VALUE",
Expand Down