cmd/login.go (120 lines of code) (raw):
// Copyright © 2017 Microsoft <wastore@microsoft.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package cmd
import (
"errors"
"strings"
"github.com/Azure/azure-storage-azcopy/v10/common"
"github.com/spf13/cobra"
)
var loginCmdArg = loginCmdArgs{tenantID: common.DefaultTenantID}
var lgCmd = &cobra.Command{
Use: "login",
SuggestFor: []string{"login"},
Short: loginCmdShortDescription,
Long: loginCmdLongDescription,
Example: loginCmdExample,
Args: func(cmd *cobra.Command, args []string) error {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
loginCmdArg.certPass = common.GetEnvironmentVariable(common.EEnvironmentVariable.CertificatePassword())
loginCmdArg.clientSecret = common.GetEnvironmentVariable(common.EEnvironmentVariable.ClientSecret())
loginCmdArg.persistToken = true
if loginCmdArg.certPass != "" || loginCmdArg.clientSecret != "" {
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
prettyErr := strings.Replace(err.Error(), `\r\n`, "\n", -1)
prettyErr += "\n\nNOTE: If your credential was created in the last 5 minutes, please wait a few minutes and try again."
glcm.Error("Failed to perform login command: \n" + prettyErr + getErrorCodeUrl(err))
}
return nil
},
}
func init() {
rootCmd.AddCommand(lgCmd)
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")
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).")
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.")
// 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.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(), ", ")+".")
// Managed Identity flags
lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityClientID, "identity-client-id", "", "Client ID of user-assigned identity.")
lgCmd.PersistentFlags().StringVar(&loginCmdArg.identityResourceID, "identity-resource-id", "", "Resource ID of user-assigned identity.")
// 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.")
// 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")
}
type loginCmdArgs struct {
// OAuth login arguments
tenantID string
aadEndpoint string
identity bool // Whether to use MSI.
servicePrincipal 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.
// https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-go
identityClientID string
identityObjectID string
identityResourceID string
//Required to sign in with a SPN (Service Principal Name)
applicationID string
certPath string
certPass string
clientSecret string
persistToken bool
}
func (lca loginCmdArgs) process() error {
// Login type consolidation to allow backward compatibility.
// Commenting the warning message till we decide on when to deprecate these options
// 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.EAutoLoginType.SPN().String()
} else if lca.identity {
lca.loginType = common.EAutoLoginType.MSI().String()
} else if lca.servicePrincipal && lca.identity {
// This isn't necessary, but stands as a sanity check. It will never be hit.
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.
lca.loginType = strings.ToLower(lca.loginType)
uotm := GetUserOAuthTokenManagerInstance()
// Persist the token to cache, if login fulfilled successfully.
switch lca.loginType {
case common.EAutoLoginType.SPN().String():
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 common.EAutoLoginType.MSI().String():
if err := uotm.MSILogin(common.IdentityInfo{
ClientID: lca.identityClientID,
ObjectID: lca.identityObjectID,
MSIResID: lca.identityResourceID,
}, lca.persistToken); err != nil {
return err
}
// For MSI login, info success message to user.
glcm.Info("Login with identity succeeded.")
case common.EAutoLoginType.AzCLI().String():
if err := uotm.AzCliLogin(lca.tenantID, lca.persistToken); err != nil {
return err
}
glcm.Info("Login with AzCliCreds succeeded")
case common.EAutoLoginType.PsCred().String():
if err := uotm.PSContextToken(lca.tenantID, lca.persistToken); err != nil {
return err
}
glcm.Info("Login with Powershell context succeeded")
case common.EAutoLoginType.Workload().String():
if err := uotm.WorkloadIdentityLogin(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
}
// User fulfills login in browser, and there would be message in browser indicating whether login fulfilled successfully.
glcm.Info("Login succeeded.")
}
return nil
}