diff --git a/digitalocean/resource_digitalocean_certificate.go b/digitalocean/resource_digitalocean_certificate.go index ef0a5100..1ab892c3 100644 --- a/digitalocean/resource_digitalocean_certificate.go +++ b/digitalocean/resource_digitalocean_certificate.go @@ -60,6 +60,9 @@ func resourceDigitalOceanCertificate() *schema.Resource { Optional: true, ForceNew: true, ConflictsWith: []string{"private_key", "leaf_certificate", "certificate_chain"}, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return d.Get("type") == "custom" + }, }, "type": { diff --git a/digitalocean/resource_digitalocean_certificate_test.go b/digitalocean/resource_digitalocean_certificate_test.go index 97c9243d..1ed63888 100644 --- a/digitalocean/resource_digitalocean_certificate_test.go +++ b/digitalocean/resource_digitalocean_certificate_test.go @@ -1,10 +1,18 @@ package digitalocean import ( + "bytes" "context" + crand "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" + "math/big" "strings" "testing" + "time" "github.com/digitalocean/godo" "github.com/hashicorp/terraform/helper/acctest" @@ -90,11 +98,11 @@ func testAccCheckDigitalOceanCertificateExists(n string, cert *godo.Certificate) } func generateTestCertMaterial(t *testing.T) (string, string, string) { - leafCertMaterial, privateKeyMaterial, err := acctest.RandTLSCert("Acme Co") + leafCertMaterial, privateKeyMaterial, err := randTLSCert("Acme Co", "example.com") if err != nil { t.Fatalf("Cannot generate test TLS certificate: %s", err) } - rootCertMaterial, _, err := acctest.RandTLSCert("Acme Go") + rootCertMaterial, _, err := randTLSCert("Acme Go", "example.com") if err != nil { t.Fatalf("Cannot generate test TLS certificate: %s", err) } @@ -103,6 +111,63 @@ func generateTestCertMaterial(t *testing.T) (string, string, string) { return privateKeyMaterial, leafCertMaterial, certChainMaterial } +// Based on Terraform's acctest.RandTLSCert, but allows for passing DNS name. +func randTLSCert(orgName string, dnsName string) (string, string, error) { + template := &x509.Certificate{ + SerialNumber: big.NewInt(int64(acctest.RandInt())), + Subject: pkix.Name{ + Organization: []string{orgName}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + DNSNames: []string{dnsName}, + } + + privateKey, privateKeyPEM, err := genPrivateKey() + if err != nil { + return "", "", err + } + + cert, err := x509.CreateCertificate(crand.Reader, template, template, &privateKey.PublicKey, privateKey) + if err != nil { + return "", "", err + } + + certPEM, err := pemEncode(cert, "CERTIFICATE") + if err != nil { + return "", "", err + } + + return certPEM, privateKeyPEM, nil +} + +func genPrivateKey() (*rsa.PrivateKey, string, error) { + privateKey, err := rsa.GenerateKey(crand.Reader, 1024) + if err != nil { + return nil, "", err + } + + privateKeyPEM, err := pemEncode(x509.MarshalPKCS1PrivateKey(privateKey), "RSA PRIVATE KEY") + if err != nil { + return nil, "", err + } + + return privateKey, privateKeyPEM, nil +} + +func pemEncode(b []byte, block string) (string, error) { + var buf bytes.Buffer + pb := &pem.Block{Type: block, Bytes: b} + if err := pem.Encode(&buf, pb); err != nil { + return "", err + } + + return buf.String(), nil +} + func testAccCheckDigitalOceanCertificateConfig_basic(rInt int, privateKeyMaterial, leafCert, certChain string) string { return fmt.Sprintf(` resource "digitalocean_certificate" "foobar" {