Skip to content

Commit e87da38

Browse files
Allow the creation of subordinate CAs (#5055) (#3499)
* Bootstrap CA pool for CA tests * Add subordinate CA example/test * fix creation of subordinate CAs Signed-off-by: Modular Magician <[email protected]>
1 parent 669562a commit e87da38

File tree

6 files changed

+224
-38
lines changed

6 files changed

+224
-38
lines changed

.changelog/5055.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
privateca: fixed a failure to create `google_privateca_certificate_authority` of type `SUBORDINATE` due to an invalid attempt to activate it on creation.
3+
```

google-beta/bootstrap_utils_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,42 @@ func BootstrapSharedSQLInstanceBackupRun(t *testing.T) string {
429429

430430
return bootstrapInstance.Name
431431
}
432+
433+
func BootstrapSharedCaPoolInLocation(t *testing.T, location string) string {
434+
project := getTestProjectFromEnv()
435+
poolName := "static-ca-pool"
436+
437+
config := BootstrapConfig(t)
438+
if config == nil {
439+
return ""
440+
}
441+
442+
log.Printf("[DEBUG] Getting shared CA pool %q", poolName)
443+
url := fmt.Sprintf("%sprojects/%s/locations/%s/caPools/%s", config.PrivatecaBasePath, project, location, poolName)
444+
_, err := sendRequest(config, "GET", project, url, config.userAgent, nil)
445+
if err != nil {
446+
log.Printf("[DEBUG] CA pool %q not found, bootstrapping", poolName)
447+
poolObj := map[string]interface{}{
448+
"tier": "ENTERPRISE",
449+
}
450+
createUrl := fmt.Sprintf("%sprojects/%s/locations/%s/caPools?caPoolId=%s", config.PrivatecaBasePath, project, location, poolName)
451+
res, err := sendRequestWithTimeout(config, "POST", project, createUrl, config.userAgent, poolObj, 4*time.Minute)
452+
if err != nil {
453+
t.Fatalf("Error bootstrapping shared CA pool %q: %s", poolName, err)
454+
}
455+
456+
log.Printf("[DEBUG] Waiting for CA pool creation to finish")
457+
var opRes map[string]interface{}
458+
err = privatecaOperationWaitTimeWithResponse(
459+
config, res, &opRes, project, "Creating CA pool", config.userAgent,
460+
4*time.Minute)
461+
if err != nil {
462+
t.Errorf("Error getting shared CA pool %q: %s", poolName, err)
463+
}
464+
res, err = sendRequest(config, "GET", project, url, config.userAgent, nil)
465+
if err != nil {
466+
t.Errorf("Error getting shared CA pool %q: %s", poolName, err)
467+
}
468+
}
469+
return poolName
470+
}

google-beta/resource_gke_hub_feature_membership_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"fmt"
66
"testing"
77

8-
"github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl"
8+
dcl "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl"
99
gkehub "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/gkehub/beta"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1111
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

google-beta/resource_privateca_certificate_authority.go

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -698,23 +698,25 @@ func resourcePrivatecaCertificateAuthorityCreate(d *schema.ResourceData, meta in
698698
}
699699
d.SetId(id)
700700

701-
url, err = replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:enable")
702-
if err != nil {
703-
return err
704-
}
701+
if d.Get("type").(string) != "SUBORDINATE" {
702+
url, err = replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:enable")
703+
if err != nil {
704+
return err
705+
}
705706

706-
log.Printf("[DEBUG] Enabling CertificateAuthority: %#v", obj)
707+
log.Printf("[DEBUG] Enabling CertificateAuthority: %#v", obj)
707708

708-
res, err = sendRequest(config, "POST", billingProject, url, userAgent, nil)
709-
if err != nil {
710-
return fmt.Errorf("Error enabling CertificateAuthority: %s", err)
711-
}
709+
res, err = sendRequest(config, "POST", billingProject, url, userAgent, nil)
710+
if err != nil {
711+
return fmt.Errorf("Error enabling CertificateAuthority: %s", err)
712+
}
712713

713-
err = privatecaOperationWaitTimeWithResponse(
714-
config, res, &opRes, project, "Enabling CertificateAuthority", userAgent,
715-
d.Timeout(schema.TimeoutCreate))
716-
if err != nil {
717-
return fmt.Errorf("Error waiting to enable CertificateAuthority: %s", err)
714+
err = privatecaOperationWaitTimeWithResponse(
715+
config, res, &opRes, project, "Enabling CertificateAuthority", userAgent,
716+
d.Timeout(schema.TimeoutCreate))
717+
if err != nil {
718+
return fmt.Errorf("Error waiting to enable CertificateAuthority: %s", err)
719+
}
718720
}
719721

720722
log.Printf("[DEBUG] Finished creating CertificateAuthority %q: %#v", d.Id(), res)
@@ -829,24 +831,26 @@ func resourcePrivatecaCertificateAuthorityDelete(d *schema.ResourceData, meta in
829831
}
830832

831833
var obj map[string]interface{}
832-
disableUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:disable")
833-
if err != nil {
834-
return err
835-
}
834+
if d.Get("state").(string) == "ENABLED" {
835+
disableUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:disable")
836+
if err != nil {
837+
return err
838+
}
836839

837-
log.Printf("[DEBUG] Disabling CertificateAuthority: %#v", obj)
840+
log.Printf("[DEBUG] Disabling CertificateAuthority: %#v", obj)
838841

839-
dRes, err := sendRequest(config, "POST", billingProject, disableUrl, userAgent, nil)
840-
if err != nil {
841-
return fmt.Errorf("Error disabling CertificateAuthority: %s", err)
842-
}
842+
dRes, err := sendRequest(config, "POST", billingProject, disableUrl, userAgent, nil)
843+
if err != nil {
844+
return fmt.Errorf("Error disabling CertificateAuthority: %s", err)
845+
}
843846

844-
var opRes map[string]interface{}
845-
err = privatecaOperationWaitTimeWithResponse(
846-
config, dRes, &opRes, project, "Disabling CertificateAuthority", userAgent,
847-
d.Timeout(schema.TimeoutDelete))
848-
if err != nil {
849-
return fmt.Errorf("Error waiting to disable CertificateAuthority: %s", err)
847+
var opRes map[string]interface{}
848+
err = privatecaOperationWaitTimeWithResponse(
849+
config, dRes, &opRes, project, "Disabling CertificateAuthority", userAgent,
850+
d.Timeout(schema.TimeoutDelete))
851+
if err != nil {
852+
return fmt.Errorf("Error waiting to disable CertificateAuthority: %s", err)
853+
}
850854
}
851855
log.Printf("[DEBUG] Deleting CertificateAuthority %q", d.Id())
852856

google-beta/resource_privateca_certificate_authority_generated_test.go

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ func TestAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityBasicExam
2727
t.Parallel()
2828

2929
context := map[string]interface{}{
30-
"pool": "static-ca-pool",
30+
"pool_name": BootstrapSharedCaPoolInLocation(t, "us-central1"),
31+
"pool_location": "us-central1",
3132
"random_suffix": randString(t, 10),
3233
}
3334

@@ -54,9 +55,9 @@ func testAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityBasicExam
5455
resource "google_privateca_certificate_authority" "default" {
5556
// This example assumes this pool already exists.
5657
// Pools cannot be deleted in normal test circumstances, so we depend on static pools
57-
pool = "%{pool}"
58+
pool = "%{pool_name}"
5859
certificate_authority_id = "tf-test-my-certificate-authority%{random_suffix}"
59-
location = "us-central1"
60+
location = "%{pool_location}"
6061
config {
6162
subject_config {
6263
subject {
@@ -101,13 +102,94 @@ resource "google_privateca_certificate_authority" "default" {
101102
`, context)
102103
}
103104

105+
func TestAccPrivatecaCertificateAuthority_privatecaCertificateAuthoritySubordinateExample(t *testing.T) {
106+
t.Parallel()
107+
108+
context := map[string]interface{}{
109+
"pool_name": BootstrapSharedCaPoolInLocation(t, "us-central1"),
110+
"pool_location": "us-central1",
111+
"random_suffix": randString(t, 10),
112+
}
113+
114+
vcrTest(t, resource.TestCase{
115+
PreCheck: func() { testAccPreCheck(t) },
116+
Providers: testAccProviders,
117+
CheckDestroy: testAccCheckPrivatecaCertificateAuthorityDestroyProducer(t),
118+
Steps: []resource.TestStep{
119+
{
120+
Config: testAccPrivatecaCertificateAuthority_privatecaCertificateAuthoritySubordinateExample(context),
121+
},
122+
{
123+
ResourceName: "google_privateca_certificate_authority.default",
124+
ImportState: true,
125+
ImportStateVerify: true,
126+
ImportStateVerifyIgnore: []string{"ignore_active_certificates_on_deletion", "location", "certificate_authority_id", "pool"},
127+
},
128+
},
129+
})
130+
}
131+
132+
func testAccPrivatecaCertificateAuthority_privatecaCertificateAuthoritySubordinateExample(context map[string]interface{}) string {
133+
return Nprintf(`
134+
resource "google_privateca_certificate_authority" "default" {
135+
// This example assumes this pool already exists.
136+
// Pools cannot be deleted in normal test circumstances, so we depend on static pools
137+
pool = "%{pool_name}"
138+
certificate_authority_id = "tf-test-my-certificate-authority%{random_suffix}"
139+
location = "%{pool_location}"
140+
config {
141+
subject_config {
142+
subject {
143+
organization = "HashiCorp"
144+
common_name = "my-subordinate-authority"
145+
}
146+
subject_alt_name {
147+
dns_names = ["hashicorp.com"]
148+
}
149+
}
150+
x509_config {
151+
ca_options {
152+
is_ca = true
153+
max_issuer_path_length = 10
154+
}
155+
key_usage {
156+
base_key_usage {
157+
digital_signature = true
158+
content_commitment = true
159+
key_encipherment = false
160+
data_encipherment = true
161+
key_agreement = true
162+
cert_sign = true
163+
crl_sign = true
164+
decipher_only = true
165+
}
166+
extended_key_usage {
167+
server_auth = true
168+
client_auth = false
169+
email_protection = true
170+
code_signing = true
171+
time_stamping = true
172+
}
173+
}
174+
}
175+
}
176+
lifetime = "86400s"
177+
key_spec {
178+
algorithm = "RSA_PKCS1_4096_SHA256"
179+
}
180+
type = "SUBORDINATE"
181+
}
182+
`, context)
183+
}
184+
104185
func TestAccPrivatecaCertificateAuthority_privatecaCertificateAuthorityByoKeyExample(t *testing.T) {
105186
skipIfVcr(t)
106187
t.Parallel()
107188

108189
context := map[string]interface{}{
109190
"kms_key_name": BootstrapKMSKeyWithPurposeInLocation(t, "ASYMMETRIC_SIGN", "us-central1").CryptoKey.Name,
110-
"pool": "static-ca-pool",
191+
"pool_name": BootstrapSharedCaPoolInLocation(t, "us-central1"),
192+
"pool_location": "us-central1",
111193
"random_suffix": randString(t, 10),
112194
}
113195

@@ -155,9 +237,9 @@ resource "google_kms_crypto_key_iam_binding" "privateca_sa_keyuser_viewer" {
155237
resource "google_privateca_certificate_authority" "default" {
156238
// This example assumes this pool already exists.
157239
// Pools cannot be deleted in normal test circumstances, so we depend on static pools
158-
pool = "%{pool}"
240+
pool = "%{pool_name}"
159241
certificate_authority_id = "tf-test-my-certificate-authority%{random_suffix}"
160-
location = "us-central1"
242+
location = "%{pool_location}"
161243
key_spec {
162244
cloud_kms_key_version = "%{kms_key_name}/cryptoKeyVersions/1"
163245
}

website/docs/r/privateca_certificate_authority.html.markdown

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ To get more information about CertificateAuthority, see:
4444
resource "google_privateca_certificate_authority" "default" {
4545
// This example assumes this pool already exists.
4646
// Pools cannot be deleted in normal test circumstances, so we depend on static pools
47-
pool = ""
47+
pool = "ca-pool"
4848
certificate_authority_id = "my-certificate-authority"
4949
location = "us-central1"
5050
config {
@@ -89,6 +89,64 @@ resource "google_privateca_certificate_authority" "default" {
8989
}
9090
}
9191
```
92+
<div class = "oics-button" style="float: right; margin: 0 0 -15px">
93+
<a href="https://console.cloud.google.com/cloudshell/open?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fterraform-google-modules%2Fdocs-examples.git&cloudshell_working_dir=privateca_certificate_authority_subordinate&cloudshell_image=gcr.io%2Fgraphite-cloud-shell-images%2Fterraform%3Alatest&open_in_editor=main.tf&cloudshell_print=.%2Fmotd&cloudshell_tutorial=.%2Ftutorial.md" target="_blank">
94+
<img alt="Open in Cloud Shell" src="//gstatic.com/cloudssh/images/open-btn.svg" style="max-height: 44px; margin: 32px auto; max-width: 100%;">
95+
</a>
96+
</div>
97+
## Example Usage - Privateca Certificate Authority Subordinate
98+
99+
100+
```hcl
101+
resource "google_privateca_certificate_authority" "default" {
102+
// This example assumes this pool already exists.
103+
// Pools cannot be deleted in normal test circumstances, so we depend on static pools
104+
pool = "ca-pool"
105+
certificate_authority_id = "my-certificate-authority"
106+
location = "us-central1"
107+
config {
108+
subject_config {
109+
subject {
110+
organization = "HashiCorp"
111+
common_name = "my-subordinate-authority"
112+
}
113+
subject_alt_name {
114+
dns_names = ["hashicorp.com"]
115+
}
116+
}
117+
x509_config {
118+
ca_options {
119+
is_ca = true
120+
max_issuer_path_length = 10
121+
}
122+
key_usage {
123+
base_key_usage {
124+
digital_signature = true
125+
content_commitment = true
126+
key_encipherment = false
127+
data_encipherment = true
128+
key_agreement = true
129+
cert_sign = true
130+
crl_sign = true
131+
decipher_only = true
132+
}
133+
extended_key_usage {
134+
server_auth = true
135+
client_auth = false
136+
email_protection = true
137+
code_signing = true
138+
time_stamping = true
139+
}
140+
}
141+
}
142+
}
143+
lifetime = "86400s"
144+
key_spec {
145+
algorithm = "RSA_PKCS1_4096_SHA256"
146+
}
147+
type = "SUBORDINATE"
148+
}
149+
```
92150
<div class = "oics-button" style="float: right; margin: 0 0 -15px">
93151
<a href="https://console.cloud.google.com/cloudshell/open?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fterraform-google-modules%2Fdocs-examples.git&cloudshell_working_dir=privateca_certificate_authority_byo_key&cloudshell_image=gcr.io%2Fgraphite-cloud-shell-images%2Fterraform%3Alatest&open_in_editor=main.tf&cloudshell_print=.%2Fmotd&cloudshell_tutorial=.%2Ftutorial.md" target="_blank">
94152
<img alt="Open in Cloud Shell" src="//gstatic.com/cloudssh/images/open-btn.svg" style="max-height: 44px; margin: 32px auto; max-width: 100%;">
@@ -122,7 +180,7 @@ resource "google_kms_crypto_key_iam_binding" "privateca_sa_keyuser_viewer" {
122180
resource "google_privateca_certificate_authority" "default" {
123181
// This example assumes this pool already exists.
124182
// Pools cannot be deleted in normal test circumstances, so we depend on static pools
125-
pool = ""
183+
pool = "ca-pool"
126184
certificate_authority_id = "my-certificate-authority"
127185
location = "us-central1"
128186
key_spec {

0 commit comments

Comments
 (0)