Skip to content

Commit

Permalink
Support subordinate CA activation via first and third party issuers. (#…
Browse files Browse the repository at this point in the history
…6161) (#4422)

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Jun 29, 2022
1 parent 57aea59 commit 22bfbbc
Show file tree
Hide file tree
Showing 8 changed files with 694 additions and 68 deletions.
3 changes: 3 additions & 0 deletions .changelog/6161.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
privateca: added support to subordinate CA activation
```
221 changes: 221 additions & 0 deletions google-beta/privateca_ca_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package google

import (
"fmt"
"log"
"math/rand"
"regexp"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// CA related utilities.

func enableCA(config *Config, d *schema.ResourceData, project string, billingProject string, userAgent string) error {
enableUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:enable")
if err != nil {
return err
}

log.Printf("[DEBUG] Enabling CertificateAuthority")

res, err := sendRequest(config, "POST", billingProject, enableUrl, userAgent, nil)
if err != nil {
return fmt.Errorf("Error enabling CertificateAuthority: %s", err)
}

var opRes map[string]interface{}
err = privatecaOperationWaitTimeWithResponse(
config, res, &opRes, project, "Enabling CertificateAuthority", userAgent,
d.Timeout(schema.TimeoutCreate))
if err != nil {
return fmt.Errorf("Error waiting to enable CertificateAuthority: %s", err)
}
return nil
}

func disableCA(config *Config, d *schema.ResourceData, project string, billingProject string, userAgent string) error {
disableUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:disable")
if err != nil {
return err
}

log.Printf("[DEBUG] Disabling CA")

dRes, err := sendRequest(config, "POST", billingProject, disableUrl, userAgent, nil)
if err != nil {
return fmt.Errorf("Error disabling CA: %s", err)
}

var opRes map[string]interface{}
err = privatecaOperationWaitTimeWithResponse(
config, dRes, &opRes, project, "Disabling CA", userAgent,
d.Timeout(schema.TimeoutDelete))
if err != nil {
return fmt.Errorf("Error waiting to disable CA: %s", err)
}
return nil
}

func activateSubCAWithThirdPartyIssuer(config *Config, d *schema.ResourceData, project string, billingProject string, userAgent string) error {
// 1. prepare parameters
signedCACert := d.Get("pem_ca_certificate").(string)

sc, ok := d.GetOk("subordinate_config")
if !ok {
return fmt.Errorf("subordinate_config is required to activate subordinate CA")
}
c := sc.([]interface{})
if len(c) == 0 || c[0] == nil {
return fmt.Errorf("subordinate_config is required to activate subordinate CA")
}
chain, ok := c[0].(map[string]interface{})["pem_issuer_chain"]
if !ok {
return fmt.Errorf("subordinate_config.pem_issuer_chain is required to activate subordinate CA with third party issuer")
}
issuerChain := chain.([]interface{})
if len(issuerChain) == 0 || issuerChain[0] == nil {
return fmt.Errorf("subordinate_config.pem_issuer_chain is required to activate subordinate CA with third party issuer")
}
pc := issuerChain[0].(map[string]interface{})["pem_certificates"].([]interface{})
pemIssuerChain := make([]string, 0, len(pc))
for _, pem := range pc {
pemIssuerChain = append(pemIssuerChain, pem.(string))
}

// 2. activate CA
activateObj := make(map[string]interface{})
activateObj["pemCaCertificate"] = signedCACert
activateObj["subordinateConfig"] = make(map[string]interface{})
activateObj["subordinateConfig"].(map[string]interface{})["pemIssuerChain"] = make(map[string]interface{})
activateObj["subordinateConfig"].(map[string]interface{})["pemIssuerChain"].(map[string]interface{})["pemCertificates"] = pemIssuerChain

activateUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:activate")
if err != nil {
return err
}

log.Printf("[DEBUG] Activating CertificateAuthority: %#v", activateObj)
res, err := sendRequest(config, "POST", billingProject, activateUrl, userAgent, activateObj)
if err != nil {
return fmt.Errorf("Error enabling CertificateAuthority: %s", err)
}

var opRes map[string]interface{}
err = privatecaOperationWaitTimeWithResponse(
config, res, &opRes, project, "Activating CertificateAuthority", userAgent,
d.Timeout(schema.TimeoutCreate))
if err != nil {
return fmt.Errorf("Error waiting to actiavte CertificateAuthority: %s", err)
}
return nil
}

func activateSubCAWithFirstPartyIssuer(config *Config, d *schema.ResourceData, project string, billingProject string, userAgent string) error {
// 1. get issuer
sc, ok := d.GetOk("subordinate_config")
if !ok {
return fmt.Errorf("subordinate_config is required to activate subordinate CA")
}
c := sc.([]interface{})
if len(c) == 0 || c[0] == nil {
return fmt.Errorf("subordinate_config is required to activate subordinate CA")
}
ca, ok := c[0].(map[string]interface{})["certificate_authority"]
if !ok {
return fmt.Errorf("subordinate_config.certificate_authority is required to activate subordinate CA with first party issuer")
}
issuer := ca.(string)

// 2. fetch CSR
fetchCSRUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:fetch")
if err != nil {
return err
}
res, err := sendRequest(config, "GET", billingProject, fetchCSRUrl, userAgent, nil)
if err != nil {
return fmt.Errorf("failed to fetch CSR: %v", err)
}
csr := res["pemCsr"]

// 3. sign the CSR with first party issuer
genCertId := func() string {
currentTime := time.Now()
dateStr := currentTime.Format("20060102")

rand.Seed(time.Now().UnixNano())
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
rand1 := make([]byte, 3)
for i := range rand1 {
rand1[i] = letters[rand.Intn(len(letters))]
}
rand2 := make([]byte, 3)
for i := range rand2 {
rand2[i] = letters[rand.Intn(len(letters))]
}
return fmt.Sprintf("subordinate-%v-%v-%v", dateStr, string(rand1), string(rand2))
}

// parseCAName parses a CA name and return the CaPool name and CaId.
parseCAName := func(n string) (string, string, error) {
parts := regexp.MustCompile(`(projects/[a-z0-9-]+/locations/[a-z0-9-]+/caPools/[a-zA-Z0-9-]+)/certificateAuthorities/([a-zA-Z0-9-]+)`).FindStringSubmatch(n)
if len(parts) != 3 {
return "", "", fmt.Errorf("failed to parse CA name: %v, parts: %v", n, parts)
}
return parts[1], parts[2], err
}

obj := make(map[string]interface{})
obj["pemCsr"] = csr
obj["lifetime"] = d.Get("lifetime")

certId := genCertId()
poolName, issuerId, err := parseCAName(issuer)
if err != nil {
return err
}

PrivatecaBasePath, err := replaceVars(d, config, "{{PrivatecaBasePath}}")
if err != nil {
return err
}
signUrl := fmt.Sprintf("%v%v/certificates?certificateId=%v", PrivatecaBasePath, poolName, certId)
signUrl, err = addQueryParams(signUrl, map[string]string{"issuingCertificateAuthorityId": issuerId})
if err != nil {
return err
}

log.Printf("[DEBUG] Signing CA Certificate: %#v", obj)
res, err = sendRequestWithTimeout(config, "POST", billingProject, signUrl, userAgent, obj, d.Timeout(schema.TimeoutCreate))
if err != nil {
return fmt.Errorf("Error creating Certificate: %s", err)
}
signedCACert := res["pemCertificate"]

// 4. activate sub CA with the signed CA cert.
activateObj := make(map[string]interface{})
activateObj["pemCaCertificate"] = signedCACert
activateObj["subordinateConfig"] = make(map[string]interface{})
activateObj["subordinateConfig"].(map[string]interface{})["certificateAuthority"] = issuer

activateUrl, err := replaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:activate")
if err != nil {
return err
}

log.Printf("[DEBUG] Activating CertificateAuthority: %#v", activateObj)
res, err = sendRequest(config, "POST", billingProject, activateUrl, userAgent, activateObj)
if err != nil {
return fmt.Errorf("Error enabling CertificateAuthority: %s", err)
}

var opRes map[string]interface{}
err = privatecaOperationWaitTimeWithResponse(
config, res, &opRes, project, "Enabling CertificateAuthority", userAgent,
d.Timeout(schema.TimeoutCreate))
if err != nil {
return fmt.Errorf("Error waiting to actiavte CertificateAuthority: %s", err)
}
return nil
}
13 changes: 8 additions & 5 deletions google-beta/resource_privateca_certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,13 @@ omitted, no template will be used. This template must be in the same location
as the Certificate.`,
},
"certificate_authority": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `Certificate Authority name.`,
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The Certificate Authority ID that should issue the certificate. For example, to issue a Certificate from
a Certificate Authority with resource name 'projects/my-project/locations/us-central1/caPools/my-pool/certificateAuthorities/my-ca',
argument 'pool' should be set to 'projects/my-project/locations/us-central1/caPools/my-pool', argument 'certificate_authority'
should be set to 'my-ca'.`,
},
"config": {
Type: schema.TypeList,
Expand Down Expand Up @@ -1129,7 +1132,7 @@ This is in RFC3339 text format.`,
"issuer_certificate_authority": {
Type: schema.TypeString,
Computed: true,
Description: `The resource name of the issuing CertificateAuthority in the format projects/*/locations/*/caPools/*/certificateAuthorities/*.`,
Description: `The resource name of the issuing CertificateAuthority in the format 'projects/*/locations/*/caPools/*/certificateAuthorities/*'.`,
},
"pem_certificate": {
Type: schema.TypeString,
Expand Down
Loading

0 comments on commit 22bfbbc

Please sign in to comment.