blob: f5305fe6238012eee5956f8234f4d027d2f0c104 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package privateca
import (
"fmt"
"log"
"math/rand"
"regexp"
"time"
"github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// CA related utilities.
func enableCA(config *transport_tpg.Config, d *schema.ResourceData, project string, billingProject string, userAgent string) error {
enableUrl, err := tpgresource.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 := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: billingProject,
RawURL: enableUrl,
UserAgent: userAgent,
})
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 *transport_tpg.Config, d *schema.ResourceData, project string, billingProject string, userAgent string) error {
disableUrl, err := tpgresource.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 := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: billingProject,
RawURL: disableUrl,
UserAgent: userAgent,
})
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 *transport_tpg.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 := tpgresource.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 := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: billingProject,
RawURL: activateUrl,
UserAgent: userAgent,
Body: 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 *transport_tpg.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 := tpgresource.ReplaceVars(d, config, "{{PrivatecaBasePath}}projects/{{project}}/locations/{{location}}/caPools/{{pool}}/certificateAuthorities/{{certificate_authority_id}}:fetch")
if err != nil {
return err
}
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: billingProject,
RawURL: fetchCSRUrl,
UserAgent: userAgent,
})
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 := tpgresource.ReplaceVars(d, config, "{{PrivatecaBasePath}}")
if err != nil {
return err
}
signUrl := fmt.Sprintf("%v%v/certificates?certificateId=%v", PrivatecaBasePath, poolName, certId)
signUrl, err = transport_tpg.AddQueryParams(signUrl, map[string]string{"issuingCertificateAuthorityId": issuerId})
if err != nil {
return err
}
log.Printf("[DEBUG] Signing CA Certificate: %#v", obj)
res, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: billingProject,
RawURL: signUrl,
UserAgent: userAgent,
Body: obj,
Timeout: 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 := tpgresource.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 = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: billingProject,
RawURL: activateUrl,
UserAgent: userAgent,
Body: 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
}
// These setters are used for tests
func (u *PrivatecaCaPoolIamUpdater) SetProject(project string) {
u.project = project
}
func (u *PrivatecaCaPoolIamUpdater) SetLocation(location string) {
u.location = location
}
func (u *PrivatecaCaPoolIamUpdater) SetCaPool(caPool string) {
u.caPool = caPool
}
func (u *PrivatecaCaPoolIamUpdater) SetResourceData(d tpgresource.TerraformResourceData) {
u.d = d
}