Skip to content

Commit a96df7b

Browse files
authored
Added support for workload identity (#2619)
1 parent d1dd6d3 commit a96df7b

11 files changed

+254
-165
lines changed

azure-pipelines.yml

+12
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,17 @@ jobs:
168168
script: 'Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -AllowClobber -Force'
169169
pwsh: 'true'
170170
displayName: 'Install Powershell Az Module'
171+
- task: AzureCLI@2
172+
displayName: 'Set Up Workload Identity Environment Variables'
173+
inputs:
174+
azureSubscription: azcopyworkloadidentity
175+
addSpnToEnvironment: true
176+
scriptType: bash
177+
scriptLocation: inlineScript
178+
inlineScript: |
179+
echo "##vso[task.setvariable variable=AZURE_CLIENT_ID]$servicePrincipalId"
180+
echo "##vso[task.setvariable variable=AZURE_FEDERATED_TOKEN]$idToken"
181+
echo "##vso[task.setvariable variable=AZURE_TENANT_ID]$tenantId"
171182
- task: GoTool@0
172183
inputs:
173184
version: $(AZCOPY_GOLANG_VERSION_COVERAGE)
@@ -228,6 +239,7 @@ jobs:
228239
NEW_E2E_CLIENT_SECRET: $(AZCOPY_NEW_E2E_CLIENT_SECRET)
229240
NEW_E2E_TENANT_ID: $(OAUTH_TENANT_ID)
230241
NEW_E2E_AZCOPY_PATH: $(System.DefaultWorkingDirectory)/$(build_name)
242+
NEW_E2E_ENVIRONMENT: "AzurePipeline"
231243
displayName: 'E2E Test $(display_name) - AMD64'
232244
233245
- task: PublishBuildArtifacts@1

cmd/credentialUtil.go

+7-21
Original file line numberDiff line numberDiff line change
@@ -104,35 +104,21 @@ func GetOAuthTokenManagerInstance() (*common.UserOAuthTokenManager, error) {
104104
}
105105

106106
// Fill up lca
107+
lca.loginType = autoLoginType
107108
switch autoLoginType {
108-
case common.AutologinTypeSPN:
109+
case common.EAutoLoginType.SPN().String():
109110
lca.applicationID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ApplicationID())
110111
lca.certPath = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.CertificatePath())
111112
lca.certPass = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.CertificatePassword())
112113
lca.clientSecret = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ClientSecret())
113-
lca.servicePrincipal = true
114-
115-
case common.AutologinTypeMSI:
114+
case common.EAutoLoginType.MSI().String():
116115
lca.identityClientID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ManagedIdentityClientID())
117116
lca.identityObjectID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ManagedIdentityObjectID())
118117
lca.identityResourceID = glcm.GetEnvironmentVariable(common.EEnvironmentVariable.ManagedIdentityResourceString())
119-
lca.identity = true
120-
121-
case common.AutologinTypeDevice:
122-
lca.identity = false
123-
124-
case common.AutologinTypeAzCLI:
125-
lca.identity = false
126-
lca.servicePrincipal = false
127-
lca.psCred = false
128-
lca.azCliCred = true
129-
130-
case common.AutologinTypePsCred:
131-
lca.identity = false
132-
lca.servicePrincipal = false
133-
lca.azCliCred = false
134-
lca.psCred = true
135-
118+
case common.EAutoLoginType.Device().String():
119+
case common.EAutoLoginType.AzCLI().String():
120+
case common.EAutoLoginType.PsCred().String():
121+
case common.EAutoLoginType.Workload().String():
136122
default:
137123
glcm.Error("Invalid Auto-login type specified: " + autoLoginType)
138124
return

cmd/login.go

+38-64
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ var lgCmd = &cobra.Command{
4848
glcm.Info(environmentVariableNotice)
4949
}
5050

51+
loginCmdArg.loginType = strings.ToLower(loginCmdArg.loginType)
52+
5153
err := loginCmdArg.process()
5254
if err != nil {
5355
// the errors from adal contains \r\n in the body, get rid of them to make the error easier to look at
@@ -65,16 +67,19 @@ func init() {
6567

6668
lgCmd.PersistentFlags().StringVar(&loginCmdArg.tenantID, "tenant-id", "", "The Azure Active Directory tenant ID to use for OAuth device interactive login.")
6769
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")
68-
// Use identity which aligns to Azure powershell and CLI.
69-
lgCmd.PersistentFlags().BoolVar(&loginCmdArg.identity, "identity", false, "Log in using virtual machine's identity, also known as managed service identity (MSI).")
70-
// Use SPN certificate to log in.
71-
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.")
72-
// Client ID of user-assigned identity.
70+
71+
lgCmd.PersistentFlags().BoolVar(&loginCmdArg.identity, "identity", false, "Deprecated. Please use --login-type=MSI. Log in using virtual machine's identity, also known as managed service identity (MSI).")
72+
lgCmd.PersistentFlags().BoolVar(&loginCmdArg.servicePrincipal, "service-principal", false, "Deprecated. Please use --login-type=SPN. 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.")
73+
// Deprecate these flags in favor of a new login type flag
74+
_ = lgCmd.PersistentFlags().MarkHidden("identity")
75+
_ = lgCmd.PersistentFlags().MarkHidden("service-principal")
76+
77+
lgCmd.PersistentFlags().StringVar(&loginCmdArg.loginType, "login-type", common.EAutoLoginType.Device().String(), "Default value is "+common.EAutoLoginType.Device().String()+". Specify the credential type to access Azure Resource, available values are "+strings.Join(common.ValidAutoLoginTypes(), ", ")+".")
78+
79+
// Managed Identity flags
7380
lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityClientID, "identity-client-id", "", "Client ID of user-assigned identity.")
74-
// Resource ID of user-assigned identity.
7581
lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityResourceID, "identity-resource-id", "", "Resource ID of user-assigned identity.")
76-
77-
//login with SPN
82+
// SPN flags
7883
lgCmd.PersistentFlags().StringVar(&loginCmdArg.applicationID, "application-id", "", "Application ID of user-assigned identity. Required for service principal auth.")
7984
lgCmd.PersistentFlags().StringVar(&loginCmdArg.certPath, "certificate-path", "", "Path to certificate for SPN authentication. Required for certificate-based service principal auth.")
8085

@@ -91,8 +96,8 @@ type loginCmdArgs struct {
9196

9297
identity bool // Whether to use MSI.
9398
servicePrincipal bool
94-
azCliCred bool
95-
psCred bool
99+
100+
loginType string
96101

97102
// Info of VM's user assigned identity, client or object ids of the service identity are required if
98103
// your VM has multiple user-assigned managed identities.
@@ -109,75 +114,39 @@ type loginCmdArgs struct {
109114
persistToken bool
110115
}
111116

112-
func (lca loginCmdArgs) validate() error {
113-
// Only support one kind of oauth login at same time.
114-
switch {
115-
case lca.identity:
116-
if lca.servicePrincipal {
117-
return errors.New("you can only log in with one type of auth at once")
118-
}
119-
120-
// Consider only command-line parameters as env vars are a hassle to change and it's not like we'll use them here.
121-
if lca.tenantID != "" || lca.applicationID != "" || lca.certPath != "" {
122-
return errors.New("tenant ID/application ID/cert path/client secret cannot be used with identity")
123-
}
124-
case lca.servicePrincipal:
125-
if lca.identity {
126-
return errors.New("you can only log in with one type of auth at once")
127-
}
128-
129-
if lca.identityClientID != "" || lca.identityObjectID != "" || lca.identityResourceID != "" {
130-
return errors.New("identity client/object/resource ID are exclusive to managed service identity auth and are not compatible with service principal auth")
131-
}
132-
133-
if lca.applicationID == "" || (lca.clientSecret == "" && lca.certPath == "") {
134-
return errors.New("service principal auth requires an application ID, and client secret/certificate")
135-
}
136-
default: // OAuth login.
137-
// This isn't necessary, but stands as a sanity check. It will never be hit.
138-
if lca.servicePrincipal || lca.identity {
139-
return errors.New("you can only log in with one type of auth at once")
140-
}
141-
142-
// Consider only command-line parameters as env vars are a hassle to change and it's not like we'll use them here.
143-
if lca.applicationID != "" || lca.certPath != "" {
144-
return errors.New("application ID and certificate paths are exclusive to service principal auth and are not compatible with OAuth")
145-
}
146-
147-
if lca.identityClientID != "" || lca.identityObjectID != "" || lca.identityResourceID != "" {
148-
return errors.New("identity client/object/resource IDs are exclusive to managed service identity auth and are not compatible with OAuth")
149-
}
150-
}
151-
152-
return nil
153-
}
154-
155117
func (lca loginCmdArgs) process() error {
156-
// Validate login parameters.
157-
if err := lca.validate(); err != nil {
158-
return err
118+
// Login type consolidation to allow backward compatibility.
119+
if lca.servicePrincipal || lca.identity {
120+
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.")
121+
}
122+
if lca.servicePrincipal {
123+
lca.loginType = common.EAutoLoginType.SPN().String()
124+
} else if lca.identity {
125+
lca.loginType = common.EAutoLoginType.MSI().String()
126+
} else if lca.servicePrincipal && lca.identity {
127+
// This isn't necessary, but stands as a sanity check. It will never be hit.
128+
return errors.New("you can only log in with one type of auth at once")
159129
}
130+
// Any required variables for login type will be validated by the Azure Identity SDK.
131+
lca.loginType = strings.ToLower(lca.loginType)
160132

161133
uotm := GetUserOAuthTokenManagerInstance()
162134
// Persist the token to cache, if login fulfilled successfully.
163135

164-
switch {
165-
case lca.servicePrincipal:
166-
136+
switch lca.loginType {
137+
case common.EAutoLoginType.SPN().String():
167138
if lca.certPath != "" {
168139
if err := uotm.CertLogin(lca.tenantID, lca.aadEndpoint, lca.certPath, lca.certPass, lca.applicationID, lca.persistToken); err != nil {
169140
return err
170141
}
171-
172142
glcm.Info("SPN Auth via cert succeeded.")
173143
} else {
174144
if err := uotm.SecretLogin(lca.tenantID, lca.aadEndpoint, lca.clientSecret, lca.applicationID, lca.persistToken); err != nil {
175145
return err
176146
}
177-
178147
glcm.Info("SPN Auth via secret succeeded.")
179148
}
180-
case lca.identity:
149+
case common.EAutoLoginType.MSI().String():
181150
if err := uotm.MSILogin(common.IdentityInfo{
182151
ClientID: lca.identityClientID,
183152
ObjectID: lca.identityObjectID,
@@ -187,16 +156,21 @@ func (lca loginCmdArgs) process() error {
187156
}
188157
// For MSI login, info success message to user.
189158
glcm.Info("Login with identity succeeded.")
190-
case lca.azCliCred:
159+
case common.EAutoLoginType.AzCLI().String():
191160
if err := uotm.AzCliLogin(lca.tenantID); err != nil {
192161
return err
193162
}
194163
glcm.Info("Login with AzCliCreds succeeded")
195-
case lca.psCred:
164+
case common.EAutoLoginType.PsCred().String():
196165
if err := uotm.PSContextToken(lca.tenantID); err != nil {
197166
return err
198167
}
199168
glcm.Info("Login with Powershell context succeeded")
169+
case common.EAutoLoginType.Workload().String():
170+
if err := uotm.WorkloadIdentityLogin(lca.persistToken); err != nil {
171+
return err
172+
}
173+
glcm.Info("Login with Workload Identity succeeded")
200174
default:
201175
if err := uotm.UserLogin(lca.tenantID, lca.aadEndpoint, lca.persistToken); err != nil {
202176
return err

common/environment.go

+44-8
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
package common
2222

2323
import (
24+
"github.com/JeffreyRichter/enum/enum"
25+
"reflect"
2426
"runtime"
27+
"strings"
2528
)
2629

2730
type EnvironmentVariable struct {
@@ -82,18 +85,51 @@ func (EnvironmentVariable) UserDir() EnvironmentVariable {
8285
}
8386
}
8487

85-
const (
86-
AutologinTypeSPN = "spn"
87-
AutologinTypeMSI = "msi"
88-
AutologinTypeDevice = "device"
89-
AutologinTypeAzCLI = "azcli"
90-
AutologinTypePsCred = "pscred"
91-
)
88+
var EAutoLoginType = AutoLoginType(0)
89+
90+
type AutoLoginType uint8
91+
92+
func (AutoLoginType) Device() AutoLoginType { return AutoLoginType(0) }
93+
func (AutoLoginType) SPN() AutoLoginType { return AutoLoginType(1) }
94+
func (AutoLoginType) MSI() AutoLoginType { return AutoLoginType(2) }
95+
func (AutoLoginType) AzCLI() AutoLoginType { return AutoLoginType(3) }
96+
func (AutoLoginType) PsCred() AutoLoginType { return AutoLoginType(4) }
97+
func (AutoLoginType) Workload() AutoLoginType { return AutoLoginType(5) }
98+
func (AutoLoginType) TokenStore() AutoLoginType { return AutoLoginType(255) } // Storage Explorer internal integration only. Do not add this to ValidAutoLoginTypes.
99+
100+
func (d AutoLoginType) String() string {
101+
return strings.ToLower(enum.StringInt(d, reflect.TypeOf(d)))
102+
}
103+
104+
func (d *AutoLoginType) Parse(s string) error {
105+
// allow empty to mean "Enable"
106+
if s == "" {
107+
*d = EAutoLoginType.Device()
108+
return nil
109+
}
110+
111+
val, err := enum.ParseInt(reflect.TypeOf(d), s, true, true)
112+
if err == nil {
113+
*d = val.(AutoLoginType)
114+
}
115+
return err
116+
}
117+
118+
func ValidAutoLoginTypes() []string {
119+
return []string{
120+
EAutoLoginType.Device().String() + " (Device code workflow)",
121+
EAutoLoginType.SPN().String() + " (Service Principal)",
122+
EAutoLoginType.MSI().String() + " (Managed Service Identity)",
123+
EAutoLoginType.AzCLI().String() + " (Azure CLI)",
124+
EAutoLoginType.PsCred().String() + " (Azure PowerShell)",
125+
EAutoLoginType.Workload().String() + " (Workload Identity)",
126+
}
127+
}
92128

93129
func (EnvironmentVariable) AutoLoginType() EnvironmentVariable {
94130
return EnvironmentVariable{
95131
Name: "AZCOPY_AUTO_LOGIN_TYPE",
96-
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.",
132+
Description: "Specify the credential type to access Azure Resource without invoking the login command and using the OS secret store, available values are " + strings.Join(ValidAutoLoginTypes(), ", ") + ".",
97133
}
98134
}
99135

0 commit comments

Comments
 (0)