diff --git a/builder/azure/arm/authenticate.go b/builder/azure/arm/authenticate.go index 7f0b5bdc9..10b8e4bd7 100644 --- a/builder/azure/arm/authenticate.go +++ b/builder/azure/arm/authenticate.go @@ -31,6 +31,10 @@ func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*a return nil, err } + if a.clientID == "" && a.clientSecret == "" { + return adal.NewServicePrincipalTokenFromMSI("http://169.254.169.254/metadata/identity/oauth2/token", resource) + } + spt, err := adal.NewServicePrincipalToken( *oauthConfig, a.clientID, diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 77daa67c0..94b385c3c 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -514,17 +514,14 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { if isUseDeviceLogin(c) { c.useDeviceLogin = true } else { - if c.ClientID == "" { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id must be specified")) + if c.ClientID == "" && c.ClientSecret != "" || c.ClientID != "" && c.ClientSecret == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_id and client_secret must be specified together or not specified at all")) } - if c.ClientSecret == "" { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("A client_secret must be specified")) + if c.ClientID != "" && c.SubscriptionID == "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified when client_id & client_secret are")) } - if c.SubscriptionID == "" { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified")) - } } ///////////////////////////////////////////// diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index d25329dc7..d9b79eabe 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -124,6 +124,87 @@ func TestConfigShouldNotDefaultImageVersionIfCustomImage(t *testing.T) { } } +func Test_newConfig_MSI(t *testing.T) { + baseConfig := map[string]string{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "os_type": constants.Target_Linux, + } + + tests := []struct { + name string + args []interface{} + wantErr bool + }{ + { + name: "no client_id and no client_secret should enable MSI auth", + args: []interface{}{ + baseConfig, + getPackerConfiguration(), + }, + wantErr: false, + }, + { + name: "subscription_id is will be taken from MSI", + args: []interface{}{ + baseConfig, + map[string]string{ + "subscription_id": "error", + }, + getPackerConfiguration(), + }, + wantErr: false, + }, + { + name: "client_id without client_secret should error", + args: []interface{}{ + baseConfig, + map[string]string{ + "client_id": "error", + }, + getPackerConfiguration(), + }, + wantErr: true, + }, + { + name: "client_secret without client_id should error", + args: []interface{}{ + baseConfig, + map[string]string{ + "client_secret": "error", + }, + getPackerConfiguration(), + }, + wantErr: true, + }, + { + name: "missing subscription_id", + args: []interface{}{ + baseConfig, + map[string]string{ + "client_id": "ok", + "client_secret": "ok", + }, + getPackerConfiguration(), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, _, err := newConfig(tt.args...) + if (err != nil) != tt.wantErr { + t.Errorf("newConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + func TestConfigShouldNormalizeOSTypeCase(t *testing.T) { config := map[string]string{ "capture_name_prefix": "ignore", diff --git a/website/source/docs/builders/azure-setup.html.md b/website/source/docs/builders/azure-setup.html.md index d5909c122..de107a78c 100644 --- a/website/source/docs/builders/azure-setup.html.md +++ b/website/source/docs/builders/azure-setup.html.md @@ -59,14 +59,30 @@ mode. > Device login mode is for the Public and US Gov clouds only. The device login flow asks that you open a web browser, navigate to -, and input the supplied code. This authorizes the -Packer for Azure application to act on your behalf. An OAuth token will be -created, and stored in the user's home directory -(~/.azure/packer/oauth-TenantID.json). This token is used if the token file -exists, and it is refreshed as necessary. The token file prevents the need to -continually execute the device login flow. Packer will ask for two device login -auth, one for service management endpoint and another for accessing temp -keyvault secrets that it creates. +http://aka.ms/devicelogin, +and input the supplied code. This authorizes the Packer for Azure application +to act on your behalf. An OAuth token will be created, and stored in the user's +home directory (\~/.azure/packer/oauth-TenantID.json). This token is used if +the token file exists, and it is refreshed as necessary. The token file +prevents the need to continually execute the device login flow. Packer will ask +for two device login auth, one for service management endpoint and another for +accessing temp keyvault secrets that it creates. + +## Managed identities for Azure resources + +-> Managed identities for Azure resources is the new name for the service +formerly known as Managed Service Identity (MSI). + +Managed identities is an alternative way to authorize in Azure Packer. Managed +identities for Azure resources are automatically managed by Azure and enable +you to authenticate to services that support Azure AD authentication without +needing to insert credentials into your buildfile. Navigate to +managed identities azure resources overview to learn more about +this feature. + +This feature will be used when no `subscription_id`, `client_id` or +`client_secret` is set in your buildfile. ## Install the Azure CLI diff --git a/website/source/docs/builders/azure.html.md b/website/source/docs/builders/azure.html.md index a81406cc7..4ba951ee5 100644 --- a/website/source/docs/builders/azure.html.md +++ b/website/source/docs/builders/azure.html.md @@ -35,7 +35,7 @@ addition to the options listed here, a [communicator](/docs/templates/communicator.html) can be configured for this builder. -### Required: +### Required ( unless instance has [managed identities](/docs/builders/azure-setup.html#managed-identities-for-azure-resources) enabled): - `client_id` (string) The Active Directory service principal associated with your builder. @@ -48,6 +48,8 @@ builder. specified in which case it needs to have owner access to the existing resource group specified in build\_resource\_group\_name parameter.** +### Required: + - `image_publisher` (string) PublisherName for your base image. See [documentation](https://azure.microsoft.com/en-us/documentation/articles/resource-groups-vm-searching/) for details. @@ -250,6 +252,7 @@ Providing `temp_resource_group_name` or `location` in combination with type* - the target must be a *Managed Image*. + "shared_image_gallery": { "subscription": "00000000-0000-0000-0000-00000000000", "resource_group": "ResourceGroup",