mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
15 KiB
471 lines
15 KiB
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package azure
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-azure-sdk/sdk/auth"
|
|
"github.com/hashicorp/go-azure-sdk/sdk/environments"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
|
"github.com/hashicorp/terraform/internal/backend/backendbase"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
// New creates a new backend for Azure remote state.
|
|
func New() backend.Backend {
|
|
return &Backend{
|
|
Base: backendbase.Base{
|
|
Schema: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"subscription_id": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The Subscription ID where the Storage Account is located.",
|
|
},
|
|
"resource_group_name": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The Resource Group where the Storage Account is located.",
|
|
},
|
|
"storage_account_name": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
Description: "The name of the storage account.",
|
|
},
|
|
"container_name": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
Description: "The container name to use in the Storage Account.",
|
|
},
|
|
"key": {
|
|
Type: cty.String,
|
|
Required: true,
|
|
Description: "The blob key to use in the Storage Container.",
|
|
},
|
|
"lookup_blob_endpoint": {
|
|
Type: cty.Bool,
|
|
Optional: true,
|
|
Description: "Whether to look up the storage account blob endpoint. This is necessary when the storage account uses the Azure DNS zone endpoint.",
|
|
},
|
|
"snapshot": {
|
|
Type: cty.Bool,
|
|
Optional: true,
|
|
Description: "Whether to enable automatic blob snapshotting.",
|
|
},
|
|
"environment": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The Cloud Environment which should be used. Possible values are public, usgovernment, and china. Defaults to public. Not used and should not be specified when `metadata_host` is specified.",
|
|
},
|
|
"metadata_host": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The Hostname which should be used for the Azure Metadata Service.",
|
|
},
|
|
"access_key": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The access key to use when authenticating using a Storage Access Key.",
|
|
},
|
|
"sas_token": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The SAS Token to use when authenticating using a SAS Token.",
|
|
},
|
|
"tenant_id": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The Tenant ID to use when authenticating using Azure Active Directory.",
|
|
},
|
|
"client_id": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The Client ID to use when authenticating using Azure Active Directory.",
|
|
},
|
|
"client_id_file_path": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The path to a file containing the Client ID which should be used.",
|
|
},
|
|
"endpoint": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Deprecated: true,
|
|
Description: "`endpoint` is deprecated in favor of `msi_endpoint`, it will be removed in a future version of Terraform",
|
|
},
|
|
|
|
// Client Certificate specific fields
|
|
"client_certificate": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "Base64 encoded PKCS#12 certificate bundle to use when authenticating as a Service Principal using a Client Certificate",
|
|
},
|
|
"client_certificate_path": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The path to the Client Certificate associated with the Service Principal for use when authenticating as a Service Principal using a Client Certificate.",
|
|
},
|
|
"client_certificate_password": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The password associated with the Client Certificate. For use when authenticating as a Service Principal using a Client Certificate",
|
|
},
|
|
|
|
// Client Secret specific fields
|
|
"client_secret": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The Client Secret which should be used. For use When authenticating as a Service Principal using a Client Secret.",
|
|
},
|
|
"client_secret_file_path": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The path to a file containing the Client Secret which should be used. For use When authenticating as a Service Principal using a Client Secret.",
|
|
},
|
|
|
|
// OIDC specific fields
|
|
"use_oidc": {
|
|
Type: cty.Bool,
|
|
Optional: true,
|
|
Description: "Allow OpenID Connect to be used for authentication",
|
|
},
|
|
"ado_pipeline_service_connection_id": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The Azure DevOps Pipeline Service Connection ID.",
|
|
},
|
|
"oidc_request_token": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The bearer token for the request to the OIDC provider. For use when authenticating as a Service Principal using OpenID Connect.",
|
|
},
|
|
"oidc_request_url": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The URL for the OIDC provider from which to request an ID token. For use when authenticating as a Service Principal using OpenID Connect.",
|
|
},
|
|
"oidc_token": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The OIDC ID token for use when authenticating as a Service Principal using OpenID Connect.",
|
|
},
|
|
"oidc_token_file_path": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The path to a file containing an OIDC ID token for use when authenticating as a Service Principal using OpenID Connect.",
|
|
},
|
|
|
|
// Managed Identity specific fields
|
|
"use_msi": {
|
|
Type: cty.Bool,
|
|
Optional: true,
|
|
Description: "Allow Managed Identity to be used for Authentication.",
|
|
},
|
|
"msi_endpoint": {
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Description: "The path to a custom endpoint for Managed Identity - in most circumstances this should be detected automatically.",
|
|
},
|
|
|
|
// Azure CLI specific fields
|
|
"use_cli": {
|
|
Type: cty.Bool,
|
|
Optional: true,
|
|
Description: "Allow Azure CLI to be used for Authentication.",
|
|
},
|
|
|
|
// Azure AKS Workload Identity fields
|
|
"use_aks_workload_identity": {
|
|
Type: cty.Bool,
|
|
Optional: true,
|
|
Description: "Allow Azure AKS Workload Identity to be used for Authentication.",
|
|
},
|
|
|
|
// Feature Flags
|
|
"use_azuread_auth": {
|
|
Type: cty.Bool,
|
|
Optional: true,
|
|
Description: "Whether to use Azure Active Directory authentication to access the Storage Data Plane APIs.",
|
|
},
|
|
},
|
|
},
|
|
SDKLikeDefaults: backendbase.SDKLikeDefaults{
|
|
"subscription_id": {
|
|
EnvVars: []string{"ARM_SUBSCRIPTION_ID"},
|
|
Fallback: "",
|
|
},
|
|
"lookup_blob_endpoint": {
|
|
EnvVars: []string{"ARM_USE_DNS_ZONE_ENDPOINT"},
|
|
Fallback: "false",
|
|
},
|
|
"snapshot": {
|
|
EnvVars: []string{"ARM_SNAPSHOT"},
|
|
Fallback: "false",
|
|
},
|
|
"environment": {
|
|
EnvVars: []string{"ARM_ENVIRONMENT"},
|
|
Fallback: "public",
|
|
},
|
|
"metadata_host": {
|
|
EnvVars: []string{"ARM_METADATA_HOSTNAME", "ARM_METADATA_HOST"}, // TODO: remove support for `METADATA_HOST` in a future version
|
|
Fallback: "",
|
|
},
|
|
"access_key": {
|
|
EnvVars: []string{"ARM_ACCESS_KEY"},
|
|
Fallback: "",
|
|
},
|
|
"sas_token": {
|
|
EnvVars: []string{"ARM_SAS_TOKEN"},
|
|
Fallback: "",
|
|
},
|
|
"tenant_id": {
|
|
EnvVars: []string{"ARM_TENANT_ID"},
|
|
Fallback: "",
|
|
},
|
|
"client_id": {
|
|
EnvVars: []string{"ARM_CLIENT_ID"},
|
|
Fallback: "",
|
|
},
|
|
"client_id_file_path": {
|
|
EnvVars: []string{"ARM_CLIENT_ID_FILE_PATH"},
|
|
// no fallback
|
|
},
|
|
|
|
// Client Certificate specific fields
|
|
"client_certificate": {
|
|
EnvVars: []string{"ARM_CLIENT_CERTIFICATE"},
|
|
Fallback: "",
|
|
},
|
|
"client_certificate_path": {
|
|
EnvVars: []string{"ARM_CLIENT_CERTIFICATE_PATH"},
|
|
Fallback: "",
|
|
},
|
|
"client_certificate_password": {
|
|
EnvVars: []string{"ARM_CLIENT_CERTIFICATE_PASSWORD"},
|
|
Fallback: "",
|
|
},
|
|
|
|
// Client Secret specific fields
|
|
"client_secret": {
|
|
EnvVars: []string{"ARM_CLIENT_SECRET"},
|
|
Fallback: "",
|
|
},
|
|
"client_secret_file_path": {
|
|
EnvVars: []string{"ARM_CLIENT_SECRET_FILE_PATH"},
|
|
// no fallback
|
|
},
|
|
|
|
// OIDC specific fields
|
|
"use_oidc": {
|
|
EnvVars: []string{"ARM_USE_OIDC"},
|
|
Fallback: "false",
|
|
},
|
|
"ado_pipeline_service_connection_id": {
|
|
EnvVars: []string{"ARM_ADO_PIPELINE_SERVICE_CONNECTION_ID", "ARM_OIDC_AZURE_SERVICE_CONNECTION_ID"},
|
|
// no fallback
|
|
},
|
|
"oidc_request_token": {
|
|
EnvVars: []string{"ARM_OIDC_REQUEST_TOKEN", "ACTIONS_ID_TOKEN_REQUEST_TOKEN", "SYSTEM_ACCESSTOKEN"},
|
|
// no fallback
|
|
},
|
|
"oidc_request_url": {
|
|
EnvVars: []string{"ARM_OIDC_REQUEST_URL", "ACTIONS_ID_TOKEN_REQUEST_URL", "SYSTEM_OIDCREQUESTURI"},
|
|
// no fallback
|
|
},
|
|
"oidc_token": {
|
|
EnvVars: []string{"ARM_OIDC_TOKEN"},
|
|
Fallback: "",
|
|
},
|
|
"oidc_token_file_path": {
|
|
EnvVars: []string{"ARM_OIDC_TOKEN_FILE_PATH"},
|
|
Fallback: "",
|
|
},
|
|
|
|
// Managed Identity specific fields
|
|
"use_msi": {
|
|
EnvVars: []string{"ARM_USE_MSI"},
|
|
Fallback: "false",
|
|
},
|
|
"msi_endpoint": {
|
|
EnvVars: []string{"ARM_MSI_ENDPOINT"},
|
|
Fallback: "",
|
|
},
|
|
|
|
// Azure CLI specific fields
|
|
"use_cli": {
|
|
EnvVars: []string{"ARM_USE_CLI"},
|
|
Fallback: "true",
|
|
},
|
|
|
|
// Azure AKS Workload Identity fields
|
|
"use_aks_workload_identity": {
|
|
EnvVars: []string{"ARM_USE_AKS_WORKLOAD_IDENTITY"},
|
|
Fallback: "false",
|
|
},
|
|
|
|
// Feature Flags
|
|
"use_azuread_auth": {
|
|
EnvVars: []string{"ARM_USE_AZUREAD"},
|
|
Fallback: "false",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
type Backend struct {
|
|
backendbase.Base
|
|
|
|
// The fields below are set from configure
|
|
apiClient *Client
|
|
containerName string
|
|
keyName string
|
|
accountName string
|
|
snapshot bool
|
|
}
|
|
|
|
type BackendConfig struct {
|
|
AuthConfig *auth.Credentials
|
|
SubscriptionID string
|
|
ResourceGroupName string
|
|
StorageAccountName string
|
|
LookupBlobEndpoint bool
|
|
AccessKey string
|
|
SasToken string
|
|
UseAzureADAuthentication bool
|
|
}
|
|
|
|
func (b *Backend) Configure(configVal cty.Value) tfdiags.Diagnostics {
|
|
// This is to make the go-azure-sdk/sdk/client Client happy.
|
|
ctx := context.Background()
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
ctx, _ = context.WithTimeout(ctx, 5*time.Minute)
|
|
}
|
|
|
|
// This backend was originally written against the legacy plugin SDK, so
|
|
// we use some shimming here to keep things working mostly the same.
|
|
data := backendbase.NewSDKLikeData(configVal)
|
|
|
|
b.containerName = data.String("container_name")
|
|
b.accountName = data.String("storage_account_name")
|
|
b.keyName = data.String("key")
|
|
b.snapshot = data.Bool("snapshot")
|
|
|
|
var clientCertificateData []byte
|
|
if encodedCert := data.String("client_certificate"); encodedCert != "" {
|
|
var err error
|
|
clientCertificateData, err = decodeCertificate(encodedCert)
|
|
if err != nil {
|
|
return backendbase.ErrorAsDiagnostics(err)
|
|
}
|
|
}
|
|
|
|
oidcToken, err := getOidcToken(&data)
|
|
if err != nil {
|
|
return backendbase.ErrorAsDiagnostics(err)
|
|
}
|
|
|
|
clientSecret, err := getClientSecret(&data)
|
|
if err != nil {
|
|
return backendbase.ErrorAsDiagnostics(err)
|
|
}
|
|
|
|
clientId, err := getClientId(&data)
|
|
if err != nil {
|
|
return backendbase.ErrorAsDiagnostics(err)
|
|
}
|
|
|
|
tenantId, err := getTenantId(&data)
|
|
if err != nil {
|
|
return backendbase.ErrorAsDiagnostics(err)
|
|
}
|
|
|
|
var (
|
|
env *environments.Environment
|
|
|
|
envName = data.String("environment")
|
|
metadataHost = data.String("metadata_host")
|
|
)
|
|
|
|
if metadataHost != "" {
|
|
logEntry("[DEBUG] Configuring cloud environment from Metadata Service at %q", metadataHost)
|
|
if env, err = environments.FromEndpoint(ctx, fmt.Sprintf("https://%s", metadataHost)); err != nil {
|
|
return backendbase.ErrorAsDiagnostics(err)
|
|
}
|
|
} else {
|
|
logEntry("[DEBUG] Configuring built-in cloud environment by name: %q", envName)
|
|
if env, err = environments.FromName(envName); err != nil {
|
|
return backendbase.ErrorAsDiagnostics(err)
|
|
}
|
|
}
|
|
|
|
var (
|
|
enableAzureCli = data.Bool("use_cli")
|
|
enableManagedIdentity = data.Bool("use_msi")
|
|
enableOidc = data.Bool("use_oidc") || data.Bool("use_aks_workload_identity")
|
|
)
|
|
|
|
authConfig := &auth.Credentials{
|
|
Environment: *env,
|
|
ClientID: *clientId,
|
|
TenantID: *tenantId,
|
|
|
|
ClientCertificateData: clientCertificateData,
|
|
ClientCertificatePath: data.String("client_certificate_path"),
|
|
ClientCertificatePassword: data.String("client_certificate_password"),
|
|
ClientSecret: *clientSecret,
|
|
|
|
OIDCAssertionToken: *oidcToken,
|
|
OIDCTokenRequestURL: data.String("oidc_request_url"),
|
|
OIDCTokenRequestToken: data.String("oidc_request_token"),
|
|
ADOPipelineServiceConnectionID: data.String("ado_pipeline_service_connection_id"),
|
|
|
|
CustomManagedIdentityEndpoint: data.String("msi_endpoint"),
|
|
|
|
EnableAuthenticatingUsingClientCertificate: true,
|
|
EnableAuthenticatingUsingClientSecret: true,
|
|
EnableAuthenticatingUsingAzureCLI: enableAzureCli,
|
|
EnableAuthenticatingUsingManagedIdentity: enableManagedIdentity,
|
|
EnableAuthenticationUsingOIDC: enableOidc,
|
|
EnableAuthenticationUsingGitHubOIDC: enableOidc,
|
|
EnableAuthenticationUsingADOPipelineOIDC: enableOidc,
|
|
}
|
|
|
|
backendConfig := BackendConfig{
|
|
AuthConfig: authConfig,
|
|
SubscriptionID: data.String("subscription_id"),
|
|
ResourceGroupName: data.String("resource_group_name"),
|
|
StorageAccountName: data.String("storage_account_name"),
|
|
LookupBlobEndpoint: data.Bool("lookup_blob_endpoint"),
|
|
AccessKey: data.String("access_key"),
|
|
SasToken: data.String("sas_token"),
|
|
UseAzureADAuthentication: data.Bool("use_azuread_auth"),
|
|
}
|
|
|
|
needToLookupAccessKey := backendConfig.AccessKey == "" && backendConfig.SasToken == "" && !backendConfig.UseAzureADAuthentication
|
|
if backendConfig.ResourceGroupName == "" {
|
|
if needToLookupAccessKey {
|
|
return backendbase.ErrorAsDiagnostics(fmt.Errorf("One of `access_key`, `sas_token`, `use_azuread_auth` and `resource_group_name` must be specified"))
|
|
}
|
|
if backendConfig.LookupBlobEndpoint {
|
|
return backendbase.ErrorAsDiagnostics(fmt.Errorf("`resource_group_name` is required when `lookup_blob_endpoint` is set"))
|
|
}
|
|
}
|
|
|
|
client, err := buildClient(ctx, backendConfig)
|
|
if err != nil {
|
|
return backendbase.ErrorAsDiagnostics(err)
|
|
}
|
|
|
|
b.apiClient = client
|
|
return nil
|
|
}
|