Skip to content

Commit d99e5c7

Browse files
Promote autokey resources to GA (#14202) (#23490)
[upstream:b73b0711add34806fd501319f8bf517f86eb2513] Signed-off-by: Modular Magician <[email protected]>
1 parent 6ad3e75 commit d99e5c7

17 files changed

+1180
-21
lines changed

.changelog/14202.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
```release-note:new-resource
2+
`google_kms_autokey_config` (ga)
3+
```
4+
5+
```release-note:new-resource
6+
`google_kms_key_handle` (ga)
7+
```
8+
9+
```release-note:new-datasource
10+
`google_kms_autokey_config` (ga)
11+
```
12+
13+
```release-note:new-datasource
14+
`google_kms_key_handle` (ga)
15+
```
16+
17+
```release-note:new-datasource
18+
`google_kms_key_handles` (ga)
19+
```

google/acctest/bootstrap_test_utils.go

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import (
2727
"testing"
2828
"time"
2929

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+
3034
"github.com/hashicorp/terraform-provider-google/google/envvar"
3135
tpgcompute "github.com/hashicorp/terraform-provider-google/google/services/compute"
3236
"github.com/hashicorp/terraform-provider-google/google/services/privateca"
@@ -50,6 +54,10 @@ import (
5054

5155
var SharedKeyRing = "tftest-shared-keyring-1"
5256

57+
var DefaultKeyHandleName = "eed58b7b-20ad-4da8-ad85-ba78a0d5ab87"
58+
var DefaultKeyHandleResourceType = "compute.googleapis.com/Disk"
59+
var CloudKmsSrviceName = "cloudkms.googleapis.com"
60+
5361
var SharedCryptoKey = map[string]string{
5462
"ENCRYPT_DECRYPT": "tftest-shared-key-1",
5563
"ASYMMETRIC_SIGN": "tftest-shared-sign-key-1",
@@ -91,6 +99,240 @@ func BootstrapKMSKeyWithPurposeInLocation(t *testing.T, purpose, locationID stri
9199
return BootstrapKMSKeyWithPurposeInLocationAndName(t, purpose, locationID, SharedCryptoKey[purpose])
92100
}
93101

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+
94336
func BootstrapKMSKeyWithPurposeInLocationAndName(t *testing.T, purpose, locationID, keyShortName string) BootstrappedKMS {
95337
config := BootstrapConfig(t)
96338
if config == nil {
@@ -645,6 +887,55 @@ func BootstrapServicePerimeterProjects(t *testing.T, desiredProjects int) []*clo
645887
return projects
646888
}
647889

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+
648939
// BootstrapProject will create or get a project named
649940
// "<projectIDPrefix><projectIDSuffix>" that will persist across test runs,
650941
// where projectIDSuffix is based off of getTestProjectFromEnv(). The reason

google/provider/provider_mmv1_resources.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ var handwrittenDatasources = map[string]*schema.Resource{
307307
"google_kms_crypto_key_versions": kms.DataSourceGoogleKmsCryptoKeyVersions(),
308308
"google_kms_key_ring": kms.DataSourceGoogleKmsKeyRing(),
309309
"google_kms_key_rings": kms.DataSourceGoogleKmsKeyRings(),
310+
"google_kms_key_handle": kms.DataSourceGoogleKmsKeyHandle(),
311+
"google_kms_autokey_config": kms.DataSourceGoogleKmsAutokeyConfig(),
312+
"google_kms_key_handles": kms.DataSourceGoogleKmsKeyHandles(),
310313
"google_kms_secret": kms.DataSourceGoogleKmsSecret(),
311314
"google_kms_secret_ciphertext": kms.DataSourceGoogleKmsSecretCiphertext(),
312315
"google_folder": resourcemanager.DataSourceGoogleFolder(),
@@ -544,9 +547,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{
544547
}
545548

546549
// Resources
547-
// Generated resources: 626
550+
// Generated resources: 628
548551
// Generated IAM resources: 309
549-
// Total generated resources: 935
552+
// Total generated resources: 937
550553
var generatedResources = map[string]*schema.Resource{
551554
"google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(),
552555
"google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(),
@@ -1179,12 +1182,14 @@ var generatedResources = map[string]*schema.Resource{
11791182
"google_integration_connectors_managed_zone": integrationconnectors.ResourceIntegrationConnectorsManagedZone(),
11801183
"google_integrations_auth_config": integrations.ResourceIntegrationsAuthConfig(),
11811184
"google_integrations_client": integrations.ResourceIntegrationsClient(),
1185+
"google_kms_autokey_config": kms.ResourceKMSAutokeyConfig(),
11821186
"google_kms_crypto_key": kms.ResourceKMSCryptoKey(),
11831187
"google_kms_crypto_key_version": kms.ResourceKMSCryptoKeyVersion(),
11841188
"google_kms_ekm_connection": kms.ResourceKMSEkmConnection(),
11851189
"google_kms_ekm_connection_iam_binding": tpgiamresource.ResourceIamBinding(kms.KMSEkmConnectionIamSchema, kms.KMSEkmConnectionIamUpdaterProducer, kms.KMSEkmConnectionIdParseFunc),
11861190
"google_kms_ekm_connection_iam_member": tpgiamresource.ResourceIamMember(kms.KMSEkmConnectionIamSchema, kms.KMSEkmConnectionIamUpdaterProducer, kms.KMSEkmConnectionIdParseFunc),
11871191
"google_kms_ekm_connection_iam_policy": tpgiamresource.ResourceIamPolicy(kms.KMSEkmConnectionIamSchema, kms.KMSEkmConnectionIamUpdaterProducer, kms.KMSEkmConnectionIdParseFunc),
1192+
"google_kms_key_handle": kms.ResourceKMSKeyHandle(),
11881193
"google_kms_key_ring": kms.ResourceKMSKeyRing(),
11891194
"google_kms_key_ring_import_job": kms.ResourceKMSKeyRingImportJob(),
11901195
"google_kms_secret_ciphertext": kms.ResourceKMSSecretCiphertext(),

0 commit comments

Comments
 (0)