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

     1  package gcs
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	kms "cloud.google.com/go/kms/apiv1"
    14  	"cloud.google.com/go/storage"
    15  	"github.com/hashicorp/terraform/internal/backend"
    16  	"github.com/hashicorp/terraform/internal/httpclient"
    17  	"github.com/hashicorp/terraform/internal/states/remote"
    18  	"google.golang.org/api/option"
    19  	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
    20  )
    21  
    22  const (
    23  	noPrefix        = ""
    24  	noEncryptionKey = ""
    25  	noKmsKeyName    = ""
    26  )
    27  
    28  // See https://cloud.google.com/storage/docs/using-encryption-keys#generating_your_own_encryption_key
    29  const encryptionKey = "yRyCOikXi1ZDNE0xN3yiFsJjg7LGimoLrGFcLZgQoVk="
    30  
    31  // KMS key ring name and key name are hardcoded here and re-used because key rings (and keys) cannot be deleted
    32  // Test code asserts their presence and creates them if they're absent. They're not deleted at the end of tests.
    33  // See: https://cloud.google.com/kms/docs/faq#cannot_delete
    34  const (
    35  	keyRingName = "tf-gcs-backend-acc-tests"
    36  	keyName     = "tf-test-key-1"
    37  	kmsRole     = "roles/cloudkms.cryptoKeyEncrypterDecrypter" // GCS service account needs this binding on the created key
    38  )
    39  
    40  var keyRingLocation = os.Getenv("GOOGLE_REGION")
    41  
    42  func TestStateFile(t *testing.T) {
    43  	t.Parallel()
    44  
    45  	cases := []struct {
    46  		prefix        string
    47  		name          string
    48  		wantStateFile string
    49  		wantLockFile  string
    50  	}{
    51  		{"state", "default", "state/default.tfstate", "state/default.tflock"},
    52  		{"state", "test", "state/test.tfstate", "state/test.tflock"},
    53  		{"state", "test", "state/test.tfstate", "state/test.tflock"},
    54  		{"state", "test", "state/test.tfstate", "state/test.tflock"},
    55  	}
    56  	for _, c := range cases {
    57  		b := &Backend{
    58  			prefix: c.prefix,
    59  		}
    60  
    61  		if got := b.stateFile(c.name); got != c.wantStateFile {
    62  			t.Errorf("stateFile(%q) = %q, want %q", c.name, got, c.wantStateFile)
    63  		}
    64  
    65  		if got := b.lockFile(c.name); got != c.wantLockFile {
    66  			t.Errorf("lockFile(%q) = %q, want %q", c.name, got, c.wantLockFile)
    67  		}
    68  	}
    69  }
    70  
    71  func TestRemoteClient(t *testing.T) {
    72  	t.Parallel()
    73  
    74  	bucket := bucketName(t)
    75  	be := setupBackend(t, bucket, noPrefix, noEncryptionKey, noKmsKeyName)
    76  	defer teardownBackend(t, be, noPrefix)
    77  
    78  	ss, err := be.StateMgr(backend.DefaultStateName)
    79  	if err != nil {
    80  		t.Fatalf("be.StateMgr(%q) = %v", backend.DefaultStateName, err)
    81  	}
    82  
    83  	rs, ok := ss.(*remote.State)
    84  	if !ok {
    85  		t.Fatalf("be.StateMgr(): got a %T, want a *remote.State", ss)
    86  	}
    87  
    88  	remote.TestClient(t, rs.Client)
    89  }
    90  func TestRemoteClientWithEncryption(t *testing.T) {
    91  	t.Parallel()
    92  
    93  	bucket := bucketName(t)
    94  	be := setupBackend(t, bucket, noPrefix, encryptionKey, noKmsKeyName)
    95  	defer teardownBackend(t, be, noPrefix)
    96  
    97  	ss, err := be.StateMgr(backend.DefaultStateName)
    98  	if err != nil {
    99  		t.Fatalf("be.StateMgr(%q) = %v", backend.DefaultStateName, err)
   100  	}
   101  
   102  	rs, ok := ss.(*remote.State)
   103  	if !ok {
   104  		t.Fatalf("be.StateMgr(): got a %T, want a *remote.State", ss)
   105  	}
   106  
   107  	remote.TestClient(t, rs.Client)
   108  }
   109  
   110  func TestRemoteLocks(t *testing.T) {
   111  	t.Parallel()
   112  
   113  	bucket := bucketName(t)
   114  	be := setupBackend(t, bucket, noPrefix, noEncryptionKey, noKmsKeyName)
   115  	defer teardownBackend(t, be, noPrefix)
   116  
   117  	remoteClient := func() (remote.Client, error) {
   118  		ss, err := be.StateMgr(backend.DefaultStateName)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  
   123  		rs, ok := ss.(*remote.State)
   124  		if !ok {
   125  			return nil, fmt.Errorf("be.StateMgr(): got a %T, want a *remote.State", ss)
   126  		}
   127  
   128  		return rs.Client, nil
   129  	}
   130  
   131  	c0, err := remoteClient()
   132  	if err != nil {
   133  		t.Fatalf("remoteClient(0) = %v", err)
   134  	}
   135  	c1, err := remoteClient()
   136  	if err != nil {
   137  		t.Fatalf("remoteClient(1) = %v", err)
   138  	}
   139  
   140  	remote.TestRemoteLocks(t, c0, c1)
   141  }
   142  
   143  func TestBackend(t *testing.T) {
   144  	t.Parallel()
   145  
   146  	bucket := bucketName(t)
   147  
   148  	be0 := setupBackend(t, bucket, noPrefix, noEncryptionKey, noKmsKeyName)
   149  	defer teardownBackend(t, be0, noPrefix)
   150  
   151  	be1 := setupBackend(t, bucket, noPrefix, noEncryptionKey, noKmsKeyName)
   152  
   153  	backend.TestBackendStates(t, be0)
   154  	backend.TestBackendStateLocks(t, be0, be1)
   155  	backend.TestBackendStateForceUnlock(t, be0, be1)
   156  }
   157  
   158  func TestBackendWithPrefix(t *testing.T) {
   159  	t.Parallel()
   160  
   161  	prefix := "test/prefix"
   162  	bucket := bucketName(t)
   163  
   164  	be0 := setupBackend(t, bucket, prefix, noEncryptionKey, noKmsKeyName)
   165  	defer teardownBackend(t, be0, prefix)
   166  
   167  	be1 := setupBackend(t, bucket, prefix+"/", noEncryptionKey, noKmsKeyName)
   168  
   169  	backend.TestBackendStates(t, be0)
   170  	backend.TestBackendStateLocks(t, be0, be1)
   171  }
   172  func TestBackendWithCustomerSuppliedEncryption(t *testing.T) {
   173  	t.Parallel()
   174  
   175  	bucket := bucketName(t)
   176  
   177  	be0 := setupBackend(t, bucket, noPrefix, encryptionKey, noKmsKeyName)
   178  	defer teardownBackend(t, be0, noPrefix)
   179  
   180  	be1 := setupBackend(t, bucket, noPrefix, encryptionKey, noKmsKeyName)
   181  
   182  	backend.TestBackendStates(t, be0)
   183  	backend.TestBackendStateLocks(t, be0, be1)
   184  }
   185  
   186  func TestBackendWithCustomerManagedKMSEncryption(t *testing.T) {
   187  	t.Parallel()
   188  
   189  	projectID := os.Getenv("GOOGLE_PROJECT")
   190  	bucket := bucketName(t)
   191  
   192  	// Taken from global variables in test file
   193  	kmsDetails := map[string]string{
   194  		"project":  projectID,
   195  		"location": keyRingLocation,
   196  		"ringName": keyRingName,
   197  		"keyName":  keyName,
   198  	}
   199  
   200  	kmsName := setupKmsKey(t, kmsDetails)
   201  
   202  	be0 := setupBackend(t, bucket, noPrefix, noEncryptionKey, kmsName)
   203  	defer teardownBackend(t, be0, noPrefix)
   204  
   205  	be1 := setupBackend(t, bucket, noPrefix, noEncryptionKey, kmsName)
   206  
   207  	backend.TestBackendStates(t, be0)
   208  	backend.TestBackendStateLocks(t, be0, be1)
   209  }
   210  
   211  // setupBackend returns a new GCS backend.
   212  func setupBackend(t *testing.T, bucket, prefix, key, kmsName string) backend.Backend {
   213  	t.Helper()
   214  
   215  	projectID := os.Getenv("GOOGLE_PROJECT")
   216  	if projectID == "" || os.Getenv("TF_ACC") == "" {
   217  		t.Skip("This test creates a bucket in GCS and populates it. " +
   218  			"Since this may incur costs, it will only run if " +
   219  			"the TF_ACC and GOOGLE_PROJECT environment variables are set.")
   220  	}
   221  
   222  	config := map[string]interface{}{
   223  		"bucket": bucket,
   224  		"prefix": prefix,
   225  	}
   226  	// Only add encryption keys to config if non-zero value set
   227  	// If not set here, default values are supplied in `TestBackendConfig` by `PrepareConfig` function call
   228  	if len(key) > 0 {
   229  		config["encryption_key"] = key
   230  	}
   231  	if len(kmsName) > 0 {
   232  		config["kms_encryption_key"] = kmsName
   233  	}
   234  
   235  	b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config))
   236  	be := b.(*Backend)
   237  
   238  	// create the bucket if it doesn't exist
   239  	bkt := be.storageClient.Bucket(bucket)
   240  	_, err := bkt.Attrs(be.storageContext)
   241  	if err != nil {
   242  		if err != storage.ErrBucketNotExist {
   243  			t.Fatal(err)
   244  		}
   245  
   246  		attrs := &storage.BucketAttrs{
   247  			Location: os.Getenv("GOOGLE_REGION"),
   248  		}
   249  		err := bkt.Create(be.storageContext, projectID, attrs)
   250  		if err != nil {
   251  			t.Fatal(err)
   252  		}
   253  	}
   254  
   255  	return b
   256  }
   257  
   258  // setupKmsKey asserts that a KMS key chain and key exist and necessary IAM bindings are in place
   259  // If the key ring or key do not exist they are created and permissions are given to the GCS Service account
   260  func setupKmsKey(t *testing.T, keyDetails map[string]string) string {
   261  	t.Helper()
   262  
   263  	projectID := os.Getenv("GOOGLE_PROJECT")
   264  	if projectID == "" || os.Getenv("TF_ACC") == "" {
   265  		t.Skip("This test creates a KMS key ring and key in Cloud KMS. " +
   266  			"Since this may incur costs, it will only run if " +
   267  			"the TF_ACC and GOOGLE_PROJECT environment variables are set.")
   268  	}
   269  
   270  	// KMS Client
   271  	ctx := context.Background()
   272  	opts, err := testGetClientOptions(t)
   273  	if err != nil {
   274  		e := fmt.Errorf("testGetClientOptions() failed: %s", err)
   275  		t.Fatal(e)
   276  	}
   277  	c, err := kms.NewKeyManagementClient(ctx, opts...)
   278  	if err != nil {
   279  		e := fmt.Errorf("kms.NewKeyManagementClient() failed: %v", err)
   280  		t.Fatal(e)
   281  	}
   282  	defer c.Close()
   283  
   284  	// Get KMS key ring, create if doesn't exist
   285  	reqGetKeyRing := &kmspb.GetKeyRingRequest{
   286  		Name: fmt.Sprintf("projects/%s/locations/%s/keyRings/%s", keyDetails["project"], keyDetails["location"], keyDetails["ringName"]),
   287  	}
   288  	var keyRing *kmspb.KeyRing
   289  	keyRing, err = c.GetKeyRing(ctx, reqGetKeyRing)
   290  	if err != nil {
   291  		if !strings.Contains(err.Error(), "NotFound") {
   292  			// Handle unexpected error that isn't related to the key ring not being made yet
   293  			t.Fatal(err)
   294  		}
   295  		// Create key ring that doesn't exist
   296  		t.Logf("Cloud KMS key ring `%s` not found: creating key ring",
   297  			fmt.Sprintf("projects/%s/locations/%s/keyRings/%s", keyDetails["project"], keyDetails["location"], keyDetails["ringName"]),
   298  		)
   299  		reqCreateKeyRing := &kmspb.CreateKeyRingRequest{
   300  			Parent:    fmt.Sprintf("projects/%s/locations/%s", keyDetails["project"], keyDetails["location"]),
   301  			KeyRingId: keyDetails["ringName"],
   302  		}
   303  		keyRing, err = c.CreateKeyRing(ctx, reqCreateKeyRing)
   304  		if err != nil {
   305  			t.Fatal(err)
   306  		}
   307  		t.Logf("Cloud KMS key ring `%s` created successfully", keyRing.Name)
   308  	}
   309  
   310  	// Get KMS key, create if doesn't exist (and give GCS service account permission to use)
   311  	reqGetKey := &kmspb.GetCryptoKeyRequest{
   312  		Name: fmt.Sprintf("%s/cryptoKeys/%s", keyRing.Name, keyDetails["keyName"]),
   313  	}
   314  	var key *kmspb.CryptoKey
   315  	key, err = c.GetCryptoKey(ctx, reqGetKey)
   316  	if err != nil {
   317  		if !strings.Contains(err.Error(), "NotFound") {
   318  			// Handle unexpected error that isn't related to the key not being made yet
   319  			t.Fatal(err)
   320  		}
   321  		// Create key that doesn't exist
   322  		t.Logf("Cloud KMS key `%s` not found: creating key",
   323  			fmt.Sprintf("%s/cryptoKeys/%s", keyRing.Name, keyDetails["keyName"]),
   324  		)
   325  		reqCreateKey := &kmspb.CreateCryptoKeyRequest{
   326  			Parent:      keyRing.Name,
   327  			CryptoKeyId: keyDetails["keyName"],
   328  			CryptoKey: &kmspb.CryptoKey{
   329  				Purpose: kmspb.CryptoKey_ENCRYPT_DECRYPT,
   330  			},
   331  		}
   332  		key, err = c.CreateCryptoKey(ctx, reqCreateKey)
   333  		if err != nil {
   334  			t.Fatal(err)
   335  		}
   336  		t.Logf("Cloud KMS key `%s` created successfully", key.Name)
   337  	}
   338  
   339  	// Get GCS Service account email, check has necessary permission on key
   340  	// Note: we cannot reuse the backend's storage client (like in the setupBackend function)
   341  	// because the KMS key needs to exist before the backend buckets are made in the test.
   342  	sc, err := storage.NewClient(ctx, opts...) //reuse opts from KMS client
   343  	if err != nil {
   344  		e := fmt.Errorf("storage.NewClient() failed: %v", err)
   345  		t.Fatal(e)
   346  	}
   347  	defer sc.Close()
   348  	gcsServiceAccount, err := sc.ServiceAccount(ctx, keyDetails["project"])
   349  	if err != nil {
   350  		t.Fatal(err)
   351  	}
   352  
   353  	// Assert Cloud Storage service account has permission to use this key.
   354  	member := fmt.Sprintf("serviceAccount:%s", gcsServiceAccount)
   355  	iamHandle := c.ResourceIAM(key.Name)
   356  	policy, err := iamHandle.Policy(ctx)
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  	if ok := policy.HasRole(member, kmsRole); !ok {
   361  		// Add the missing permissions
   362  		t.Logf("Granting GCS service account %s %s role on key %s", gcsServiceAccount, kmsRole, key.Name)
   363  		policy.Add(member, kmsRole)
   364  		err = iamHandle.SetPolicy(ctx, policy)
   365  		if err != nil {
   366  			t.Fatal(err)
   367  		}
   368  	}
   369  	return key.Name
   370  }
   371  
   372  // teardownBackend deletes all states from be except the default state.
   373  func teardownBackend(t *testing.T, be backend.Backend, prefix string) {
   374  	t.Helper()
   375  	gcsBE, ok := be.(*Backend)
   376  	if !ok {
   377  		t.Fatalf("be is a %T, want a *gcsBackend", be)
   378  	}
   379  	ctx := gcsBE.storageContext
   380  
   381  	bucket := gcsBE.storageClient.Bucket(gcsBE.bucketName)
   382  	objs := bucket.Objects(ctx, nil)
   383  
   384  	for o, err := objs.Next(); err == nil; o, err = objs.Next() {
   385  		if err := bucket.Object(o.Name).Delete(ctx); err != nil {
   386  			log.Printf("Error trying to delete object: %s %s\n\n", o.Name, err)
   387  		} else {
   388  			log.Printf("Object deleted: %s", o.Name)
   389  		}
   390  	}
   391  
   392  	// Delete the bucket itself.
   393  	if err := bucket.Delete(ctx); err != nil {
   394  		t.Errorf("deleting bucket %q failed, manual cleanup may be required: %v", gcsBE.bucketName, err)
   395  	}
   396  }
   397  
   398  // bucketName returns a valid bucket name for this test.
   399  func bucketName(t *testing.T) string {
   400  	name := fmt.Sprintf("tf-%x-%s", time.Now().UnixNano(), t.Name())
   401  
   402  	// Bucket names must contain 3 to 63 characters.
   403  	if len(name) > 63 {
   404  		name = name[:63]
   405  	}
   406  
   407  	return strings.ToLower(name)
   408  }
   409  
   410  // getClientOptions returns the []option.ClientOption needed to configure Google API clients
   411  // that are required in acceptance tests but are not part of the gcs backend itself
   412  func testGetClientOptions(t *testing.T) ([]option.ClientOption, error) {
   413  	t.Helper()
   414  
   415  	var creds string
   416  	if v := os.Getenv("GOOGLE_BACKEND_CREDENTIALS"); v != "" {
   417  		creds = v
   418  	} else {
   419  		creds = os.Getenv("GOOGLE_CREDENTIALS")
   420  	}
   421  	if creds == "" {
   422  		t.Skip("This test required credentials to be supplied via" +
   423  			"the GOOGLE_CREDENTIALS or GOOGLE_BACKEND_CREDENTIALS environment variables.")
   424  	}
   425  
   426  	var opts []option.ClientOption
   427  	var credOptions []option.ClientOption
   428  
   429  	contents, err := backend.ReadPathOrContents(creds)
   430  	if err != nil {
   431  		return nil, fmt.Errorf("error loading credentials: %s", err)
   432  	}
   433  	if !json.Valid([]byte(contents)) {
   434  		return nil, fmt.Errorf("the string provided in credentials is neither valid json nor a valid file path")
   435  	}
   436  	credOptions = append(credOptions, option.WithCredentialsJSON([]byte(contents)))
   437  	opts = append(opts, credOptions...)
   438  	opts = append(opts, option.WithUserAgent(httpclient.UserAgentString()))
   439  
   440  	return opts, nil
   441  }