@@ -27,6 +27,10 @@ import (
27
27
"testing"
28
28
"time"
29
29
30
+ "github.com/hashicorp/terraform-provider-google/google/services/kms"
31
+ tpgservicusage "github.com/hashicorp/terraform-provider-google/google/services/serviceusage"
32
+ resourceManagerV3 "google.golang.org/api/cloudresourcemanager/v3"
33
+
30
34
"github.com/hashicorp/terraform-provider-google/google/envvar"
31
35
tpgcompute "github.com/hashicorp/terraform-provider-google/google/services/compute"
32
36
"github.com/hashicorp/terraform-provider-google/google/services/privateca"
@@ -50,6 +54,10 @@ import (
50
54
51
55
var SharedKeyRing = "tftest-shared-keyring-1"
52
56
57
+ var DefaultKeyHandleName = "eed58b7b-20ad-4da8-ad85-ba78a0d5ab87"
58
+ var DefaultKeyHandleResourceType = "compute.googleapis.com/Disk"
59
+ var CloudKmsSrviceName = "cloudkms.googleapis.com"
60
+
53
61
var SharedCryptoKey = map [string ]string {
54
62
"ENCRYPT_DECRYPT" : "tftest-shared-key-1" ,
55
63
"ASYMMETRIC_SIGN" : "tftest-shared-sign-key-1" ,
@@ -91,6 +99,240 @@ func BootstrapKMSKeyWithPurposeInLocation(t *testing.T, purpose, locationID stri
91
99
return BootstrapKMSKeyWithPurposeInLocationAndName (t , purpose , locationID , SharedCryptoKey [purpose ])
92
100
}
93
101
102
+ type BootstrappedKMSAutokey struct {
103
+ * cloudkms.AutokeyConfig
104
+ * cloudkms.KeyHandle
105
+ }
106
+
107
+ func BootstrapKMSAutokeyKeyHandle (t * testing.T ) BootstrappedKMSAutokey {
108
+ return BootstrapKMSAutokeyKeyHandleWithLocation (t , "global" )
109
+ }
110
+
111
+ func BootstrapKMSAutokeyKeyHandleWithLocation (t * testing.T , locationID string ) BootstrappedKMSAutokey {
112
+ config := BootstrapConfig (t )
113
+ if config == nil {
114
+ return BootstrappedKMSAutokey {
115
+ & cloudkms.AutokeyConfig {},
116
+ & cloudkms.KeyHandle {},
117
+ }
118
+ }
119
+
120
+ autokeyFolder , kmsProject , resourceProject := setupAutokeyTestResources (t , config )
121
+
122
+ // Enable autokey on autokey test folder
123
+ kmsClient := config .NewKmsClient (config .UserAgent )
124
+ autokeyConfigID := fmt .Sprintf ("%s/autokeyConfig" , autokeyFolder .Name )
125
+ autokeyConfig , err := kmsClient .Folders .UpdateAutokeyConfig (autokeyConfigID , & cloudkms.AutokeyConfig {
126
+ KeyProject : fmt .Sprintf ("projects/%s" , kmsProject .ProjectId ),
127
+ }).UpdateMask ("keyProject" ).Do ()
128
+ if err != nil {
129
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot enable autokey on autokey test folder: %s" , err )
130
+ }
131
+
132
+ keyHandleParent := fmt .Sprintf ("projects/%s/locations/%s" , resourceProject .ProjectId , locationID )
133
+ keyHandleName := fmt .Sprintf ("%s/keyHandles/%s" , keyHandleParent , DefaultKeyHandleName )
134
+
135
+ // Get or Create the hard coded keyHandle for testing
136
+ keyHandle , err := kmsClient .Projects .Locations .KeyHandles .Get (keyHandleName ).Do ()
137
+
138
+ if err != nil {
139
+ if transport_tpg .IsGoogleApiErrorWithCode (err , 404 ) {
140
+ newKeyHandle := cloudkms.KeyHandle {
141
+ ResourceTypeSelector : DefaultKeyHandleResourceType ,
142
+ }
143
+
144
+ keyHandleOp , err := kmsClient .Projects .Locations .KeyHandles .Create (keyHandleParent , & newKeyHandle ).KeyHandleId (DefaultKeyHandleName ).Do ()
145
+ if err != nil {
146
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot create new KeyHandle: %s" , err )
147
+ }
148
+
149
+ opAsMap , err := tpgresource .ConvertToMap (keyHandleOp )
150
+ if err != nil {
151
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot get operation map: %s" , err )
152
+ }
153
+
154
+ var response map [string ]interface {}
155
+ err = kms .KMSOperationWaitTimeWithResponse (config , opAsMap , & response , resourceProject .ProjectId , "creating keyHandle" , config .UserAgent , time .Duration (5 )* time .Minute )
156
+ if err != nil {
157
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot wait for create keyhandle operation: %s" , err )
158
+ }
159
+ keyHandle = & cloudkms.KeyHandle {
160
+ Name : response ["name" ].(string ),
161
+ KmsKey : response ["kmsKey" ].(string ),
162
+ ResourceTypeSelector : response ["resourceTypeSelector" ].(string ),
163
+ }
164
+ } else {
165
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot call KeyHandle service: %s" , err )
166
+ }
167
+ }
168
+
169
+ if keyHandle == nil {
170
+ t .Fatalf ("unable to bootstrap KMS keyHandle. KeyHandle is nil!" )
171
+ }
172
+
173
+ return BootstrappedKMSAutokey {
174
+ autokeyConfig ,
175
+ keyHandle ,
176
+ }
177
+ }
178
+
179
+ func setupAutokeyTestResources (t * testing.T , config * transport_tpg.Config ) (* resourceManagerV3.Folder , * cloudresourcemanager.Project , * cloudresourcemanager.Project ) {
180
+ projectIDSuffix := strings .Replace (envvar .GetTestProjectFromEnv (), "ci-test-project-" , "" , 1 )
181
+ defaultAutokeyTestFolderName := fmt .Sprintf ("autokeytest-%s-fd" , projectIDSuffix )
182
+ defaultAutokeyTestKmsProject := fmt .Sprintf ("test-kms-%s-prj" , projectIDSuffix )
183
+ defaultAutokeyTestResourceProject := fmt .Sprintf ("test-res-%s-prj" , projectIDSuffix )
184
+
185
+ curUserEmail , err := transport_tpg .GetCurrentUserEmail (config , config .UserAgent )
186
+ if err != nil {
187
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot get current usr: %s" , err )
188
+ }
189
+ // create a folder to configure autokey config and resource folder
190
+ autokeyFolder := BootstrapFolder (t , defaultAutokeyTestFolderName )
191
+ parent := & cloudresourcemanager.ResourceId {
192
+ Type : "folder" ,
193
+ Id : strings .Split (autokeyFolder .Name , "/" )[1 ],
194
+ }
195
+ // create and setup kms project for hosting keyring and keys for autokey
196
+ kmsProject := BootstrapProjectWithParent (t , defaultAutokeyTestKmsProject , envvar .GetTestBillingAccountFromEnv (t ), parent , []string {CloudKmsSrviceName })
197
+ kmsProjectID := fmt .Sprintf ("projects/%s" , kmsProject .ProjectId )
198
+ kmsSAEmail , err := GenerateCloudKmsServiceIdentity (config , fmt .Sprintf ("%v" , kmsProject .ProjectNumber ))
199
+ if err != nil {
200
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot create cloudkms service identity: %s" , err )
201
+ }
202
+ err = addFolderBinding2 (config .NewResourceManagerV3Client (config .UserAgent ), autokeyFolder .Name , fmt .Sprintf ("user:%s" , curUserEmail ), []string {"roles/cloudkms.admin" })
203
+ if err != nil {
204
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot assign cloudkms.admin role to current user on autokey test folder: %s" , err )
205
+ }
206
+ err = addProjectBinding (config .NewResourceManagerV3Client (config .UserAgent ), kmsProjectID , fmt .Sprintf ("user:%s" , curUserEmail ), []string {"roles/resourcemanager.projectIamAdmin" , "roles/cloudkms.admin" })
207
+ if err != nil {
208
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot assign cloudkms.admin and projectIamAdmin role to current user on kms project: %s" , err )
209
+ }
210
+ err = addProjectBinding (config .NewResourceManagerV3Client (config .UserAgent ), kmsProjectID , fmt .Sprintf ("serviceAccount:%s" , kmsSAEmail ), []string {"roles/cloudkms.admin" })
211
+ if err != nil {
212
+ t .Errorf ("unable to bootstrap KMS keyHandle. Cannot assign cloudkms.admin role to cloudkms service identity on kms project: %s" , err )
213
+ }
214
+
215
+ // create and setup resource folder to host keyhandle
216
+ resourceProject := BootstrapProjectWithParent (t , defaultAutokeyTestResourceProject , envvar .GetTestBillingAccountFromEnv (t ), parent , []string {})
217
+ return autokeyFolder , kmsProject , resourceProject
218
+ }
219
+
220
+ // GenerateCloudKmsServiceIdentity generates cloud kms service identity within a project
221
+ func GenerateCloudKmsServiceIdentity (config * transport_tpg.Config , projectNum string ) (string , error ) {
222
+ url := fmt .Sprintf ("https://serviceusage.googleapis.com/v1beta1/projects/%s/services/%s:generateServiceIdentity" , projectNum , CloudKmsSrviceName )
223
+
224
+ res , err := transport_tpg .SendRequest (transport_tpg.SendRequestOptions {
225
+ Config : config ,
226
+ Method : "POST" ,
227
+ Project : projectNum ,
228
+ RawURL : url ,
229
+ UserAgent : config .UserAgent ,
230
+ Timeout : time .Minute * 4 ,
231
+ })
232
+ if err != nil {
233
+ return "" , fmt .Errorf ("error creating cloudkms service identity: %s" , err )
234
+ }
235
+
236
+ var opRes map [string ]interface {}
237
+ err = tpgservicusage .ServiceUsageOperationWaitTimeWithResponse (
238
+ config , res , & opRes , projectNum , "Creating cloudkms service identity" , config .UserAgent ,
239
+ time .Minute * 4 )
240
+ if err != nil {
241
+ return "" , err
242
+ }
243
+ return fmt .
Sprintf (
"service-%[email protected] " ,
projectNum ),
nil
244
+ }
245
+
246
+ func addProjectBinding (crmService * resourceManagerV3.Service , projectID string , member string , roles []string ) error {
247
+ return addBinding (crmService , "project" , projectID , member , roles )
248
+ }
249
+
250
+ func addFolderBinding2 (crmService * resourceManagerV3.Service , folderID string , member string , roles []string ) error {
251
+ return addBinding (crmService , "folder" , folderID , member , roles )
252
+ }
253
+
254
+ // addBinding adds the member to the project's IAM policy
255
+ func addBinding (crmService * resourceManagerV3.Service , resourceType string , resourceID string , member string , roles []string ) error {
256
+
257
+ policy , err := getPolicy (crmService , resourceType , resourceID )
258
+ if err != nil {
259
+ return err
260
+ }
261
+
262
+ // Find the policy binding for role. Only one binding can have the role.
263
+ var binding * resourceManagerV3.Binding
264
+ for _ , role := range roles {
265
+ for _ , b := range policy .Bindings {
266
+ if b .Role == role {
267
+ binding = b
268
+ break
269
+ }
270
+ }
271
+
272
+ if binding != nil {
273
+ // If the binding exists, adds the member to the binding
274
+ binding .Members = append (binding .Members , member )
275
+ } else {
276
+ // If the binding does not exist, adds a new binding to the policy
277
+ binding = & resourceManagerV3.Binding {
278
+ Role : role ,
279
+ Members : []string {member },
280
+ }
281
+ policy .Bindings = append (policy .Bindings , binding )
282
+ }
283
+ }
284
+ setPolicy (crmService , resourceType , resourceID , policy )
285
+ return nil
286
+ }
287
+
288
+ // getPolicy gets the IAM policy on input resourceID
289
+ // resourceType can be "project" or "folder"
290
+ func getPolicy (crmService * resourceManagerV3.Service , resourceType string , resourceID string ) (* resourceManagerV3.Policy , error ) {
291
+
292
+ ctx := context .Background ()
293
+
294
+ ctx , cancel := context .WithTimeout (ctx , time .Second * 10 )
295
+ defer cancel ()
296
+ request := new (resourceManagerV3.GetIamPolicyRequest )
297
+ var policy * resourceManagerV3.Policy
298
+ var err error
299
+ if resourceType == "project" {
300
+ policy , err = crmService .Projects .GetIamPolicy (resourceID , request ).Do ()
301
+ } else if resourceType == "folder" {
302
+ policy , err = crmService .Folders .GetIamPolicy (resourceID , request ).Do ()
303
+ } else {
304
+ return nil , fmt .Errorf ("invalid resourceType, supported values: project or folder" )
305
+ }
306
+ if err != nil {
307
+ return nil , fmt .Errorf ("error getting iam policy: %s" , err )
308
+ }
309
+ return policy , nil
310
+ }
311
+
312
+ // setPolicy sets the IAM policy on input resourceID
313
+ // resourceType can be "project" or "folder"
314
+ func setPolicy (crmService * resourceManagerV3.Service , resourceType string , resourceID string , policy * resourceManagerV3.Policy ) error {
315
+
316
+ ctx := context .Background ()
317
+
318
+ ctx , cancel := context .WithTimeout (ctx , time .Second * 10 )
319
+ defer cancel ()
320
+ request := new (resourceManagerV3.SetIamPolicyRequest )
321
+ request .Policy = policy
322
+ var err error
323
+ if resourceType == "project" {
324
+ _ , err = crmService .Projects .SetIamPolicy (resourceID , request ).Do ()
325
+ } else if resourceType == "folder" {
326
+ _ , err = crmService .Folders .SetIamPolicy (resourceID , request ).Do ()
327
+ } else {
328
+ return fmt .Errorf ("invalid resourceType, supported values: project or folder" )
329
+ }
330
+ if err != nil {
331
+ return fmt .Errorf ("error setting iam policy: %s" , err )
332
+ }
333
+ return nil
334
+ }
335
+
94
336
func BootstrapKMSKeyWithPurposeInLocationAndName (t * testing.T , purpose , locationID , keyShortName string ) BootstrappedKMS {
95
337
config := BootstrapConfig (t )
96
338
if config == nil {
@@ -645,6 +887,55 @@ func BootstrapServicePerimeterProjects(t *testing.T, desiredProjects int) []*clo
645
887
return projects
646
888
}
647
889
890
+ // BootstrapFolder creates or get a folder having a input folderDisplayName within a TestOrgEnv
891
+ func BootstrapFolder (t * testing.T , folderDisplayName string ) * resourceManagerV3.Folder {
892
+ config := BootstrapConfig (t )
893
+ if config == nil {
894
+ return nil
895
+ }
896
+
897
+ crmClient := config .NewResourceManagerV3Client (config .UserAgent )
898
+ searchQuery := fmt .Sprintf ("displayName=%s" , folderDisplayName )
899
+ folderSearchResp , err := crmClient .Folders .Search ().Query (searchQuery ).Do ()
900
+ if err != nil {
901
+ t .Fatalf ("error searching for folder with displayName: %s" , folderDisplayName )
902
+ }
903
+ var folder * resourceManagerV3.Folder
904
+ if len (folderSearchResp .Folders ) == 0 {
905
+ op , err := crmClient .Folders .Create (& resourceManagerV3.Folder {
906
+ DisplayName : folderDisplayName ,
907
+ Parent : fmt .Sprintf ("organizations/%s" , envvar .GetTestOrgFromEnv (t )),
908
+ }).Do ()
909
+ if err != nil {
910
+ t .Fatalf ("error bootstrapping test folder: %s" , err )
911
+ }
912
+
913
+ opAsMap , err := tpgresource .ConvertToMap (op )
914
+ if err != nil {
915
+ t .Fatalf ("error converting folder operation map: %s" , err )
916
+ }
917
+ var responseMap map [string ]interface {}
918
+ err = resourcemanager .ResourceManagerOperationWaitTimeWithResponse (config , opAsMap , & responseMap , "creating folder" , config .UserAgent , 4 * time .Minute )
919
+ if err != nil {
920
+ t .Fatalf ("error waiting for create folder operation: %s" , err )
921
+ }
922
+ folder , err = crmClient .Folders .Get (responseMap ["name" ].(string )).Do ()
923
+ if err != nil {
924
+ t .Fatalf ("error getting folder: %s" , err )
925
+ }
926
+ } else {
927
+ folder = folderSearchResp .Folders [0 ]
928
+ }
929
+
930
+ if folder .State == "DELETE_REQUESTED" {
931
+ _ , err := crmClient .Folders .Undelete (folder .Name , & resourceManagerV3.UndeleteFolderRequest {}).Do ()
932
+ if err != nil {
933
+ t .Fatalf ("error undeleting folder: %s" , err )
934
+ }
935
+ }
936
+ return folder
937
+ }
938
+
648
939
// BootstrapProject will create or get a project named
649
940
// "<projectIDPrefix><projectIDSuffix>" that will persist across test runs,
650
941
// where projectIDSuffix is based off of getTestProjectFromEnv(). The reason
0 commit comments