github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/http/backend.go (about)

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"errors"
     8  	"fmt"
     9  	"log"
    10  	"net/http"
    11  	"net/url"
    12  	"time"
    13  
    14  	"github.com/hashicorp/go-retryablehttp"
    15  
    16  	"github.com/hashicorp/terraform/internal/backend"
    17  	"github.com/hashicorp/terraform/internal/legacy/helper/schema"
    18  	"github.com/hashicorp/terraform/internal/logging"
    19  	"github.com/hashicorp/terraform/internal/states/remote"
    20  	"github.com/hashicorp/terraform/internal/states/statemgr"
    21  )
    22  
    23  func New() backend.Backend {
    24  	s := &schema.Backend{
    25  		Schema: map[string]*schema.Schema{
    26  			"address": &schema.Schema{
    27  				Type:        schema.TypeString,
    28  				Required:    true,
    29  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_ADDRESS", nil),
    30  				Description: "The address of the REST endpoint",
    31  			},
    32  			"update_method": &schema.Schema{
    33  				Type:        schema.TypeString,
    34  				Optional:    true,
    35  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_UPDATE_METHOD", "POST"),
    36  				Description: "HTTP method to use when updating state",
    37  			},
    38  			"lock_address": &schema.Schema{
    39  				Type:        schema.TypeString,
    40  				Optional:    true,
    41  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_LOCK_ADDRESS", nil),
    42  				Description: "The address of the lock REST endpoint",
    43  			},
    44  			"unlock_address": &schema.Schema{
    45  				Type:        schema.TypeString,
    46  				Optional:    true,
    47  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_UNLOCK_ADDRESS", nil),
    48  				Description: "The address of the unlock REST endpoint",
    49  			},
    50  			"lock_method": &schema.Schema{
    51  				Type:        schema.TypeString,
    52  				Optional:    true,
    53  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_LOCK_METHOD", "LOCK"),
    54  				Description: "The HTTP method to use when locking",
    55  			},
    56  			"unlock_method": &schema.Schema{
    57  				Type:        schema.TypeString,
    58  				Optional:    true,
    59  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_UNLOCK_METHOD", "UNLOCK"),
    60  				Description: "The HTTP method to use when unlocking",
    61  			},
    62  			"username": &schema.Schema{
    63  				Type:        schema.TypeString,
    64  				Optional:    true,
    65  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_USERNAME", nil),
    66  				Description: "The username for HTTP basic authentication",
    67  			},
    68  			"password": &schema.Schema{
    69  				Type:        schema.TypeString,
    70  				Optional:    true,
    71  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_PASSWORD", nil),
    72  				Description: "The password for HTTP basic authentication",
    73  			},
    74  			"skip_cert_verification": &schema.Schema{
    75  				Type:        schema.TypeBool,
    76  				Optional:    true,
    77  				Default:     false,
    78  				Description: "Whether to skip TLS verification.",
    79  			},
    80  			"retry_max": &schema.Schema{
    81  				Type:        schema.TypeInt,
    82  				Optional:    true,
    83  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_MAX", 2),
    84  				Description: "The number of HTTP request retries.",
    85  			},
    86  			"retry_wait_min": &schema.Schema{
    87  				Type:        schema.TypeInt,
    88  				Optional:    true,
    89  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_WAIT_MIN", 1),
    90  				Description: "The minimum time in seconds to wait between HTTP request attempts.",
    91  			},
    92  			"retry_wait_max": &schema.Schema{
    93  				Type:        schema.TypeInt,
    94  				Optional:    true,
    95  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_RETRY_WAIT_MAX", 30),
    96  				Description: "The maximum time in seconds to wait between HTTP request attempts.",
    97  			},
    98  			"client_ca_certificate_pem": &schema.Schema{
    99  				Type:        schema.TypeString,
   100  				Optional:    true,
   101  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CA_CERTIFICATE_PEM", ""),
   102  				Description: "A PEM-encoded CA certificate chain used by the client to verify server certificates during TLS authentication.",
   103  			},
   104  			"client_certificate_pem": &schema.Schema{
   105  				Type:        schema.TypeString,
   106  				Optional:    true,
   107  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_CERTIFICATE_PEM", ""),
   108  				Description: "A PEM-encoded certificate used by the server to verify the client during mutual TLS (mTLS) authentication.",
   109  			},
   110  			"client_private_key_pem": &schema.Schema{
   111  				Type:        schema.TypeString,
   112  				Optional:    true,
   113  				DefaultFunc: schema.EnvDefaultFunc("TF_HTTP_CLIENT_PRIVATE_KEY_PEM", ""),
   114  				Description: "A PEM-encoded private key, required if client_certificate_pem is specified.",
   115  			},
   116  		},
   117  	}
   118  
   119  	b := &Backend{Backend: s}
   120  	b.Backend.ConfigureFunc = b.configure
   121  	return b
   122  }
   123  
   124  type Backend struct {
   125  	*schema.Backend
   126  
   127  	client *httpClient
   128  }
   129  
   130  // configureTLS configures TLS when needed; if there are no conditions requiring TLS, no change is made.
   131  func (b *Backend) configureTLS(client *retryablehttp.Client, data *schema.ResourceData) error {
   132  	// If there are no conditions needing to configure TLS, leave the client untouched
   133  	skipCertVerification := data.Get("skip_cert_verification").(bool)
   134  	clientCACertificatePem := data.Get("client_ca_certificate_pem").(string)
   135  	clientCertificatePem := data.Get("client_certificate_pem").(string)
   136  	clientPrivateKeyPem := data.Get("client_private_key_pem").(string)
   137  	if !skipCertVerification && clientCACertificatePem == "" && clientCertificatePem == "" && clientPrivateKeyPem == "" {
   138  		return nil
   139  	}
   140  	if clientCertificatePem != "" && clientPrivateKeyPem == "" {
   141  		return fmt.Errorf("client_certificate_pem is set but client_private_key_pem is not")
   142  	}
   143  	if clientPrivateKeyPem != "" && clientCertificatePem == "" {
   144  		return fmt.Errorf("client_private_key_pem is set but client_certificate_pem is not")
   145  	}
   146  
   147  	// TLS configuration is needed; create an object and configure it
   148  	var tlsConfig tls.Config
   149  	client.HTTPClient.Transport.(*http.Transport).TLSClientConfig = &tlsConfig
   150  
   151  	if skipCertVerification {
   152  		// ignores TLS verification
   153  		tlsConfig.InsecureSkipVerify = true
   154  	}
   155  	if clientCACertificatePem != "" {
   156  		// trust servers based on a CA
   157  		tlsConfig.RootCAs = x509.NewCertPool()
   158  		if !tlsConfig.RootCAs.AppendCertsFromPEM([]byte(clientCACertificatePem)) {
   159  			return errors.New("failed to append certs")
   160  		}
   161  	}
   162  	if clientCertificatePem != "" && clientPrivateKeyPem != "" {
   163  		// attach a client certificate to the TLS handshake (aka mTLS)
   164  		certificate, err := tls.X509KeyPair([]byte(clientCertificatePem), []byte(clientPrivateKeyPem))
   165  		if err != nil {
   166  			return fmt.Errorf("cannot load client certificate: %w", err)
   167  		}
   168  		tlsConfig.Certificates = []tls.Certificate{certificate}
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func (b *Backend) configure(ctx context.Context) error {
   175  	data := schema.FromContextBackendConfig(ctx)
   176  
   177  	address := data.Get("address").(string)
   178  	updateURL, err := url.Parse(address)
   179  	if err != nil {
   180  		return fmt.Errorf("failed to parse address URL: %s", err)
   181  	}
   182  	if updateURL.Scheme != "http" && updateURL.Scheme != "https" {
   183  		return fmt.Errorf("address must be HTTP or HTTPS")
   184  	}
   185  
   186  	updateMethod := data.Get("update_method").(string)
   187  
   188  	var lockURL *url.URL
   189  	if v, ok := data.GetOk("lock_address"); ok && v.(string) != "" {
   190  		var err error
   191  		lockURL, err = url.Parse(v.(string))
   192  		if err != nil {
   193  			return fmt.Errorf("failed to parse lockAddress URL: %s", err)
   194  		}
   195  		if lockURL.Scheme != "http" && lockURL.Scheme != "https" {
   196  			return fmt.Errorf("lockAddress must be HTTP or HTTPS")
   197  		}
   198  	}
   199  
   200  	lockMethod := data.Get("lock_method").(string)
   201  
   202  	var unlockURL *url.URL
   203  	if v, ok := data.GetOk("unlock_address"); ok && v.(string) != "" {
   204  		var err error
   205  		unlockURL, err = url.Parse(v.(string))
   206  		if err != nil {
   207  			return fmt.Errorf("failed to parse unlockAddress URL: %s", err)
   208  		}
   209  		if unlockURL.Scheme != "http" && unlockURL.Scheme != "https" {
   210  			return fmt.Errorf("unlockAddress must be HTTP or HTTPS")
   211  		}
   212  	}
   213  
   214  	unlockMethod := data.Get("unlock_method").(string)
   215  
   216  	rClient := retryablehttp.NewClient()
   217  	rClient.RetryMax = data.Get("retry_max").(int)
   218  	rClient.RetryWaitMin = time.Duration(data.Get("retry_wait_min").(int)) * time.Second
   219  	rClient.RetryWaitMax = time.Duration(data.Get("retry_wait_max").(int)) * time.Second
   220  	rClient.Logger = log.New(logging.LogOutput(), "", log.Flags())
   221  	if err = b.configureTLS(rClient, data); err != nil {
   222  		return err
   223  	}
   224  
   225  	b.client = &httpClient{
   226  		URL:          updateURL,
   227  		UpdateMethod: updateMethod,
   228  
   229  		LockURL:      lockURL,
   230  		LockMethod:   lockMethod,
   231  		UnlockURL:    unlockURL,
   232  		UnlockMethod: unlockMethod,
   233  
   234  		Username: data.Get("username").(string),
   235  		Password: data.Get("password").(string),
   236  
   237  		// accessible only for testing use
   238  		Client: rClient,
   239  	}
   240  	return nil
   241  }
   242  
   243  func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
   244  	if name != backend.DefaultStateName {
   245  		return nil, backend.ErrWorkspacesNotSupported
   246  	}
   247  
   248  	return &remote.State{Client: b.client}, nil
   249  }
   250  
   251  func (b *Backend) Workspaces() ([]string, error) {
   252  	return nil, backend.ErrWorkspacesNotSupported
   253  }
   254  
   255  func (b *Backend) DeleteWorkspace(string, bool) error {
   256  	return backend.ErrWorkspacesNotSupported
   257  }