github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/azure/backend.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package azure
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  
    12  	"github.com/opentofu/opentofu/internal/backend"
    13  	"github.com/opentofu/opentofu/internal/encryption"
    14  	"github.com/opentofu/opentofu/internal/legacy/helper/schema"
    15  )
    16  
    17  // New creates a new backend for Azure remote state.
    18  func New(enc encryption.StateEncryption) backend.Backend {
    19  	s := &schema.Backend{
    20  		Schema: map[string]*schema.Schema{
    21  			"storage_account_name": {
    22  				Type:        schema.TypeString,
    23  				Required:    true,
    24  				Description: "The name of the storage account.",
    25  			},
    26  
    27  			"container_name": {
    28  				Type:        schema.TypeString,
    29  				Required:    true,
    30  				Description: "The container name.",
    31  			},
    32  
    33  			"key": {
    34  				Type:        schema.TypeString,
    35  				Required:    true,
    36  				Description: "The blob key.",
    37  			},
    38  
    39  			"metadata_host": {
    40  				Type:        schema.TypeString,
    41  				Required:    true,
    42  				DefaultFunc: schema.EnvDefaultFunc("ARM_METADATA_HOST", ""),
    43  				Description: "The Metadata URL which will be used to obtain the Cloud Environment.",
    44  			},
    45  
    46  			"environment": {
    47  				Type:        schema.TypeString,
    48  				Optional:    true,
    49  				Description: "The Azure cloud environment.",
    50  				DefaultFunc: schema.EnvDefaultFunc("ARM_ENVIRONMENT", "public"),
    51  			},
    52  
    53  			"access_key": {
    54  				Type:        schema.TypeString,
    55  				Optional:    true,
    56  				Description: "The access key.",
    57  				DefaultFunc: schema.EnvDefaultFunc("ARM_ACCESS_KEY", ""),
    58  			},
    59  
    60  			"sas_token": {
    61  				Type:        schema.TypeString,
    62  				Optional:    true,
    63  				Description: "A SAS Token used to interact with the Blob Storage Account.",
    64  				DefaultFunc: schema.EnvDefaultFunc("ARM_SAS_TOKEN", ""),
    65  			},
    66  
    67  			"snapshot": {
    68  				Type:        schema.TypeBool,
    69  				Optional:    true,
    70  				Description: "Enable/Disable automatic blob snapshotting",
    71  				DefaultFunc: schema.EnvDefaultFunc("ARM_SNAPSHOT", false),
    72  			},
    73  
    74  			"resource_group_name": {
    75  				Type:        schema.TypeString,
    76  				Optional:    true,
    77  				Description: "The resource group name.",
    78  			},
    79  
    80  			"client_id": {
    81  				Type:        schema.TypeString,
    82  				Optional:    true,
    83  				Description: "The Client ID.",
    84  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
    85  			},
    86  
    87  			"endpoint": {
    88  				Type:        schema.TypeString,
    89  				Optional:    true,
    90  				Description: "A custom Endpoint used to access the Azure Resource Manager API's.",
    91  				DefaultFunc: schema.EnvDefaultFunc("ARM_ENDPOINT", ""),
    92  			},
    93  
    94  			"subscription_id": {
    95  				Type:        schema.TypeString,
    96  				Optional:    true,
    97  				Description: "The Subscription ID.",
    98  				DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
    99  			},
   100  
   101  			"tenant_id": {
   102  				Type:        schema.TypeString,
   103  				Optional:    true,
   104  				Description: "The Tenant ID.",
   105  				DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
   106  			},
   107  
   108  			// Service Principal (Client Certificate) specific
   109  			"client_certificate_password": {
   110  				Type:        schema.TypeString,
   111  				Optional:    true,
   112  				Description: "The password associated with the Client Certificate specified in `client_certificate_path`",
   113  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_CERTIFICATE_PASSWORD", ""),
   114  			},
   115  			"client_certificate_path": {
   116  				Type:        schema.TypeString,
   117  				Optional:    true,
   118  				Description: "The path to the PFX file used as the Client Certificate when authenticating as a Service Principal",
   119  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_CERTIFICATE_PATH", ""),
   120  			},
   121  
   122  			// Service Principal (Client Secret) specific
   123  			"client_secret": {
   124  				Type:        schema.TypeString,
   125  				Optional:    true,
   126  				Description: "The Client Secret.",
   127  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
   128  			},
   129  
   130  			// Managed Service Identity specific
   131  			"use_msi": {
   132  				Type:        schema.TypeBool,
   133  				Optional:    true,
   134  				Description: "Should Managed Service Identity be used?",
   135  				DefaultFunc: schema.EnvDefaultFunc("ARM_USE_MSI", false),
   136  			},
   137  			"msi_endpoint": {
   138  				Type:        schema.TypeString,
   139  				Optional:    true,
   140  				Description: "The Managed Service Identity Endpoint.",
   141  				DefaultFunc: schema.EnvDefaultFunc("ARM_MSI_ENDPOINT", ""),
   142  			},
   143  
   144  			// OIDC auth specific fields
   145  			"use_oidc": {
   146  				Type:        schema.TypeBool,
   147  				Optional:    true,
   148  				DefaultFunc: schema.EnvDefaultFunc("ARM_USE_OIDC", false),
   149  				Description: "Allow OIDC to be used for authentication",
   150  			},
   151  			"oidc_token": {
   152  				Type:        schema.TypeString,
   153  				Optional:    true,
   154  				DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_TOKEN", ""),
   155  				Description: "A generic JWT token that can be used for OIDC authentication. Should not be used in conjunction with `oidc_request_token`.",
   156  			},
   157  			"oidc_token_file_path": {
   158  				Type:        schema.TypeString,
   159  				Optional:    true,
   160  				DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_TOKEN_FILE_PATH", ""),
   161  				Description: "Path to file containing a generic JWT token that can be used for OIDC authentication. Should not be used in conjunction with `oidc_request_token`.",
   162  			},
   163  			"oidc_request_url": {
   164  				Type:        schema.TypeString,
   165  				Optional:    true,
   166  				DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_REQUEST_URL", "ACTIONS_ID_TOKEN_REQUEST_URL"}, ""),
   167  				Description: "The URL of the OIDC provider from which to request an ID token. Needs to be used in conjunction with `oidc_request_token`. This is meant to be used for Github Actions.",
   168  			},
   169  			"oidc_request_token": {
   170  				Type:        schema.TypeString,
   171  				Optional:    true,
   172  				DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_REQUEST_TOKEN", "ACTIONS_ID_TOKEN_REQUEST_TOKEN"}, ""),
   173  				Description: "The bearer token to use for the request to the OIDC providers `oidc_request_url` URL to fetch an ID token. Needs to be used in conjunction with `oidc_request_url`. This is meant to be used for Github Actions.",
   174  			},
   175  
   176  			// Feature Flags
   177  			"use_azuread_auth": {
   178  				Type:        schema.TypeBool,
   179  				Optional:    true,
   180  				Description: "Should OpenTofu use AzureAD Authentication to access the Blob?",
   181  				DefaultFunc: schema.EnvDefaultFunc("ARM_USE_AZUREAD", false),
   182  			},
   183  		},
   184  	}
   185  
   186  	result := &Backend{Backend: s, encryption: enc}
   187  	result.Backend.ConfigureFunc = result.configure
   188  	return result
   189  }
   190  
   191  type Backend struct {
   192  	*schema.Backend
   193  	encryption encryption.StateEncryption
   194  
   195  	// The fields below are set from configure
   196  	armClient     *ArmClient
   197  	containerName string
   198  	keyName       string
   199  	accountName   string
   200  	snapshot      bool
   201  }
   202  
   203  type BackendConfig struct {
   204  	// Required
   205  	StorageAccountName string
   206  
   207  	// Optional
   208  	AccessKey                     string
   209  	ClientID                      string
   210  	ClientCertificatePassword     string
   211  	ClientCertificatePath         string
   212  	ClientSecret                  string
   213  	CustomResourceManagerEndpoint string
   214  	MetadataHost                  string
   215  	Environment                   string
   216  	MsiEndpoint                   string
   217  	OIDCToken                     string
   218  	OIDCTokenFilePath             string
   219  	OIDCRequestURL                string
   220  	OIDCRequestToken              string
   221  	ResourceGroupName             string
   222  	SasToken                      string
   223  	SubscriptionID                string
   224  	TenantID                      string
   225  	UseMsi                        bool
   226  	UseOIDC                       bool
   227  	UseAzureADAuthentication      bool
   228  }
   229  
   230  func (b *Backend) configure(ctx context.Context) error {
   231  	if b.containerName != "" {
   232  		return nil
   233  	}
   234  
   235  	// Grab the resource data
   236  	data := schema.FromContextBackendConfig(ctx)
   237  	b.containerName = data.Get("container_name").(string)
   238  	b.accountName = data.Get("storage_account_name").(string)
   239  	b.keyName = data.Get("key").(string)
   240  	b.snapshot = data.Get("snapshot").(bool)
   241  
   242  	config := BackendConfig{
   243  		AccessKey:                     data.Get("access_key").(string),
   244  		ClientID:                      data.Get("client_id").(string),
   245  		ClientCertificatePassword:     data.Get("client_certificate_password").(string),
   246  		ClientCertificatePath:         data.Get("client_certificate_path").(string),
   247  		ClientSecret:                  data.Get("client_secret").(string),
   248  		CustomResourceManagerEndpoint: data.Get("endpoint").(string),
   249  		MetadataHost:                  data.Get("metadata_host").(string),
   250  		Environment:                   data.Get("environment").(string),
   251  		MsiEndpoint:                   data.Get("msi_endpoint").(string),
   252  		OIDCToken:                     data.Get("oidc_token").(string),
   253  		OIDCTokenFilePath:             data.Get("oidc_token_file_path").(string),
   254  		OIDCRequestURL:                data.Get("oidc_request_url").(string),
   255  		OIDCRequestToken:              data.Get("oidc_request_token").(string),
   256  		ResourceGroupName:             data.Get("resource_group_name").(string),
   257  		SasToken:                      data.Get("sas_token").(string),
   258  		StorageAccountName:            data.Get("storage_account_name").(string),
   259  		SubscriptionID:                data.Get("subscription_id").(string),
   260  		TenantID:                      data.Get("tenant_id").(string),
   261  		UseMsi:                        data.Get("use_msi").(bool),
   262  		UseOIDC:                       data.Get("use_oidc").(bool),
   263  		UseAzureADAuthentication:      data.Get("use_azuread_auth").(bool),
   264  	}
   265  
   266  	armClient, err := buildArmClient(context.TODO(), config)
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	thingsNeededToLookupAccessKeySpecified := config.AccessKey == "" && config.SasToken == "" && config.ResourceGroupName == ""
   272  	if thingsNeededToLookupAccessKeySpecified && !config.UseAzureADAuthentication {
   273  		return fmt.Errorf("Either an Access Key / SAS Token or the Resource Group for the Storage Account must be specified - or Azure AD Authentication must be enabled")
   274  	}
   275  
   276  	b.armClient = armClient
   277  	return nil
   278  }