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 }