diff --git a/.go-version b/.go-version index a23a1564..19fb7bd9 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.17.8 +1.17.9 diff --git a/README.md b/README.md index 8e771bd3..7a797ce5 100644 --- a/README.md +++ b/README.md @@ -66,28 +66,13 @@ If [running tests and acceptance tests](#testing) isn't enough, it's possible to to use a development builds of the provider. This can be achieved by leveraging the Terraform CLI [configuration file development overrides](https://www.terraform.io/cli/config/config-file#development-overrides-for-provider-developers). -First, use `make install` to place a fresh development build of the provider in your [`${GOBIN}`](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies) (defaults to `${GOPATH}/bin` or `${HOME}/go/bin` if `${GOPATH}` is not set). Repeat +First, use `make install` to place a fresh development build of the provider in your +[`${GOBIN}`](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies) +(defaults to `${GOPATH}/bin` or `${HOME}/go/bin` if `${GOPATH}` is not set). Repeat this every time you make changes to the provider locally. -Then, in your `${HOME}/.terraformrc` (Unix) / `%APPDATA%\terraform.rc` (Windows), a `provider_installation` that contains -the following `dev_overrides`: - -```hcl -provider_installation { - dev_overrides { - "hashicorp/tls" = "${GOBIN}" //< replace `${GOBIN}` with the actual path on your system - } - - direct {} -} -``` - -Note that it's also possible to use a dedicated Terraform configuration file and invoke `terraform` while setting -the environment variable `TF_CLI_CONFIG_FILE=my_terraform_config_file`. - -Once the `dev_overrides` are in place, any local execution of `terraform plan` and `terraform apply` will -use the version of the provider found in the given `${GOBIN}` directory, -instead of the one indicated in your terraform configuration. +Then, setup your environment following [these instructions](https://www.terraform.io/plugin/debugging#terraform-cli-development-overrides) +to make your local terraform use your local build. ### Testing GitHub Actions diff --git a/internal/openssh/lib_test.go b/internal/openssh/lib_test.go index 521797fa..88b26370 100644 --- a/internal/openssh/lib_test.go +++ b/internal/openssh/lib_test.go @@ -7,8 +7,9 @@ import ( "crypto/rand" "crypto/rsa" "encoding/pem" - "golang.org/x/crypto/ssh" "testing" + + "golang.org/x/crypto/ssh" ) func TestOpenSSHFormat_MarshalAndUnmarshal_RSA(t *testing.T) { diff --git a/internal/provider/data_source_public_key_test.go b/internal/provider/data_source_public_key_test.go index 04a699fe..54ae25f7 100644 --- a/internal/provider/data_source_public_key_test.go +++ b/internal/provider/data_source_public_key_test.go @@ -36,6 +36,7 @@ func TestAccPublicKey_dataSource_PEM(t *testing.T) { resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_pem", strings.TrimSpace(testPublicKeyPEM)+"\n"), resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_openssh", strings.TrimSpace(testPublicKeyOpenSSH)+"\n"), resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_fingerprint_md5", strings.TrimSpace(testPublicKeyOpenSSHFingerprintMD5)), + resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_fingerprint_sha256", strings.TrimSpace(testPublicKeyOpenSSHFingerprintSHA256)), resource.TestCheckResourceAttr("data.tls_public_key.test", "algorithm", "RSA"), ), }, @@ -89,6 +90,7 @@ func TestAccPublicKey_dataSource_OpenSSHPEM(t *testing.T) { resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_pem", strings.TrimSpace(testPublicKeyPEM)+"\n"), resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_openssh", strings.TrimSpace(testPublicKeyOpenSSH)+"\n"), resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_fingerprint_md5", strings.TrimSpace(testPublicKeyOpenSSHFingerprintMD5)), + resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_fingerprint_sha256", strings.TrimSpace(testPublicKeyOpenSSHFingerprintSHA256)), resource.TestCheckResourceAttr("data.tls_public_key.test", "algorithm", "RSA"), ), }, diff --git a/internal/provider/fixtures_test.go b/internal/provider/fixtures_test.go index 1232f830..c600c0ca 100644 --- a/internal/provider/fixtures_test.go +++ b/internal/provider/fixtures_test.go @@ -65,8 +65,9 @@ RhFs18D3wBDBqXLIoP7W3rm5S292/JiNPa+mX76IYFF416zTBGG9J5w4d4VFrROn 8IuMWqHgdXsCUf2szN7EnJcVBsBzTxxWqz4DjX315vbm/PFOLlKzC0Ngs4h1iDiC D9Hk2MajZuFnJiqj1QIDAQAB -----END PUBLIC KEY-----` - testPublicKeyOpenSSH = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDPLaq43D9C596ko9yQipWUf2FbRhFs18D3wBDBqXLIoP7W3rm5S292/JiNPa+mX76IYFF416zTBGG9J5w4d4VFrROn8IuMWqHgdXsCUf2szN7EnJcVBsBzTxxWqz4DjX315vbm/PFOLlKzC0Ngs4h1iDiCD9Hk2MajZuFnJiqj1Q==` - testPublicKeyOpenSSHFingerprintMD5 = `62:c2:c6:7a:d0:27:72:e7:0d:bc:4e:97:42:0e:9e:e6` + testPublicKeyOpenSSH = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDPLaq43D9C596ko9yQipWUf2FbRhFs18D3wBDBqXLIoP7W3rm5S292/JiNPa+mX76IYFF416zTBGG9J5w4d4VFrROn8IuMWqHgdXsCUf2szN7EnJcVBsBzTxxWqz4DjX315vbm/PFOLlKzC0Ngs4h1iDiCD9Hk2MajZuFnJiqj1Q==` + testPublicKeyOpenSSHFingerprintMD5 = `62:c2:c6:7a:d0:27:72:e7:0d:bc:4e:97:42:0e:9e:e6` + testPublicKeyOpenSSHFingerprintSHA256 = `SHA256:V5XlMMAMdN4T4S2uBqiXBuI2C9VPNG2J8a5r1Vb8Vn8` // NOTE: See ../scripts/make-test-ca.tf for a Terraform script to create the following CA Private Key and Certificate. testCAPrivateKey = ` diff --git a/internal/provider/resource_cert_request_test.go b/internal/provider/resource_cert_request_test.go index 583e5a1f..05e5e196 100644 --- a/internal/provider/resource_cert_request_test.go +++ b/internal/provider/resource_cert_request_test.go @@ -1,14 +1,13 @@ package provider import ( - "crypto/x509" - "encoding/pem" + "crypto/x509/pkix" "fmt" - "strings" + "net" + "net/url" "testing" r "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestCertRequest(t *testing.T) { @@ -49,86 +48,41 @@ func TestCertRequest(t *testing.T) { %s EOT } - output "key_pem_1" { - value = "${tls_cert_request.test1.cert_request_pem}" - } `, testPrivateKeyPEM), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE REQUEST----") { - return fmt.Errorf("key is missing CSR PEM preamble") - } - block, _ := pem.Decode([]byte(got)) - csr, err := x509.ParseCertificateRequest(block.Bytes) - if err != nil { - return fmt.Errorf("error parsing CSR: %s", err) - } - if expected, got := "2", csr.Subject.SerialNumber; got != expected { - return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected, got) - } - if expected, got := "example.com", csr.Subject.CommonName; got != expected { - return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected, got) - } - if expected, got := "Example, Inc", csr.Subject.Organization[0]; got != expected { - return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected, got) - } - if expected, got := "Department of Terraform Testing", csr.Subject.OrganizationalUnit[0]; got != expected { - return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected, got) - } - if expected, got := "5879 Cotton Link", csr.Subject.StreetAddress[0]; got != expected { - return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected, got) - } - if expected, got := "Pirate Harbor", csr.Subject.Locality[0]; got != expected { - return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected, got) - } - if expected, got := "CA", csr.Subject.Province[0]; got != expected { - return fmt.Errorf("incorrect subject province: expected %v, got %v", expected, got) - } - if expected, got := "US", csr.Subject.Country[0]; got != expected { - return fmt.Errorf("incorrect subject country: expected %v, got %v", expected, got) - } - if expected, got := "95559-1227", csr.Subject.PostalCode[0]; got != expected { - return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(csr.DNSNames); got != expected { - return fmt.Errorf("incorrect number of DNS names: expected %v, got %v", expected, got) - } - if expected, got := "example.com", csr.DNSNames[0]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - if expected, got := "example.net", csr.DNSNames[1]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(csr.IPAddresses); got != expected { - return fmt.Errorf("incorrect number of IP addresses: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.1", csr.IPAddresses[0].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.2", csr.IPAddresses[1].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(csr.URIs); got != expected { - return fmt.Errorf("incorrect number of URIs: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/workload", csr.URIs[0].String(); got != expected { - return fmt.Errorf("incorrect URI 0: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/workload2", csr.URIs[1].String(); got != expected { - return fmt.Errorf("incorrect URI 1: expected %v, got %v", expected, got) - } - - return nil - }, + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_cert_request.test1", "cert_request_pem", PreambleCertificateRequest), + testCheckPEMCertificateRequestSubject("tls_cert_request.test1", "cert_request_pem", &pkix.Name{ + SerialNumber: "2", + CommonName: "example.com", + Organization: []string{"Example, Inc"}, + OrganizationalUnit: []string{"Department of Terraform Testing"}, + StreetAddress: []string{"5879 Cotton Link"}, + Locality: []string{"Pirate Harbor"}, + Province: []string{"CA"}, + Country: []string{"US"}, + PostalCode: []string{"95559-1227"}, + }), + testCheckPEMCertificateRequestDNSNames("tls_cert_request.test1", "cert_request_pem", []string{ + "example.com", + "example.net", + }), + testCheckPEMCertificateRequestIPAddresses("tls_cert_request.test1", "cert_request_pem", []net.IP{ + net.ParseIP("127.0.0.1"), + net.ParseIP("127.0.0.2"), + }), + testCheckPEMCertificateRequestURIs("tls_cert_request.test1", "cert_request_pem", []*url.URL{ + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "workload", + }, + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "workload2", + }, + }), + ), }, { Config: fmt.Sprintf(` @@ -141,62 +95,16 @@ EOT %s EOT } - output "key_pem_2" { - value = "${tls_cert_request.test2.cert_request_pem}" - } `, testPrivateKeyPEM), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_2"].Value - - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_2\" is not a string") - } - - if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE REQUEST----") { - return fmt.Errorf("key is missing CSR PEM preamble") - } - block, _ := pem.Decode([]byte(got)) - csr, err := x509.ParseCertificateRequest(block.Bytes) - if err != nil { - return fmt.Errorf("error parsing CSR: %s", err) - } - if expected, got := "42", csr.Subject.SerialNumber; got != expected { - return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected, got) - } - if expected, got := "", csr.Subject.CommonName; got != expected { - return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.Subject.Organization); got != expected { - return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.Subject.OrganizationalUnit); got != expected { - return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.Subject.StreetAddress); got != expected { - return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.Subject.Locality); got != expected { - return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.Subject.Province); got != expected { - return fmt.Errorf("incorrect subject province: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.Subject.Country); got != expected { - return fmt.Errorf("incorrect subject country: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.Subject.PostalCode); got != expected { - return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.DNSNames); got != expected { - return fmt.Errorf("incorrect number of DNS names: expected %v, got %v", expected, got) - } - if expected, got := 0, len(csr.IPAddresses); got != expected { - return fmt.Errorf("incorrect number of IP addresses: expected %v, got %v", expected, got) - } - - return nil - }, + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_cert_request.test2", "cert_request_pem", PreambleCertificateRequest), + testCheckPEMCertificateRequestSubject("tls_cert_request.test2", "cert_request_pem", &pkix.Name{ + SerialNumber: "42", + }), + testCheckPEMCertificateRequestDNSNames("tls_cert_request.test2", "cert_request_pem", []string{}), + testCheckPEMCertificateRequestIPAddresses("tls_cert_request.test2", "cert_request_pem", []net.IP{}), + testCheckPEMCertificateRequestURIs("tls_cert_request.test2", "cert_request_pem", []*url.URL{}), + ), }, }, }) diff --git a/internal/provider/resource_locally_signed_cert_test.go b/internal/provider/resource_locally_signed_cert_test.go index 9d18fbf6..5df99508 100644 --- a/internal/provider/resource_locally_signed_cert_test.go +++ b/internal/provider/resource_locally_signed_cert_test.go @@ -2,15 +2,14 @@ package provider import ( "crypto/x509" - "encoding/pem" + "crypto/x509/pkix" "fmt" - "regexp" - "strings" + "net" + "net/url" "testing" "time" r "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestLocallySignedCert(t *testing.T) { @@ -18,126 +17,48 @@ func TestLocallySignedCert(t *testing.T) { ProviderFactories: testProviders, Steps: []r.TestStep{ { - Config: locallySignedCertConfig(1, 0, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") { - return fmt.Errorf("key is missing cert PEM preamble") - } - block, _ := pem.Decode([]byte(got)) - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("error parsing cert: %s", err) - } - if expected, got := "2", cert.Subject.SerialNumber; got != expected { - return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected, got) - } - if expected, got := "example.com", cert.Subject.CommonName; got != expected { - return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected, got) - } - if expected, got := "Example, Inc", cert.Subject.Organization[0]; got != expected { - return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected, got) - } - if expected, got := "Department of Terraform Testing", cert.Subject.OrganizationalUnit[0]; got != expected { - return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected, got) - } - if expected, got := "5879 Cotton Link", cert.Subject.StreetAddress[0]; got != expected { - return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected, got) - } - if expected, got := "Pirate Harbor", cert.Subject.Locality[0]; got != expected { - return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected, got) - } - if expected, got := "CA", cert.Subject.Province[0]; got != expected { - return fmt.Errorf("incorrect subject province: expected %v, got %v", expected, got) - } - if expected, got := "US", cert.Subject.Country[0]; got != expected { - return fmt.Errorf("incorrect subject country: expected %v, got %v", expected, got) - } - if expected, got := "95559-1227", cert.Subject.PostalCode[0]; got != expected { - return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.DNSNames); got != expected { - return fmt.Errorf("incorrect number of DNS names: expected %v, got %v", expected, got) - } - if expected, got := "example.com", cert.DNSNames[0]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - if expected, got := "example.net", cert.DNSNames[1]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.IPAddresses); got != expected { - return fmt.Errorf("incorrect number of IP addresses: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.1", cert.IPAddresses[0].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.2", cert.IPAddresses[1].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - if expected, got := 2, len(cert.URIs); got != expected { - return fmt.Errorf("incorrect number of URIs: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/workload", cert.URIs[0].String(); got != expected { - return fmt.Errorf("incorrect URI 0: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/workload2", cert.URIs[1].String(); got != expected { - return fmt.Errorf("incorrect URI 1: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.ExtKeyUsage); got != expected { - return fmt.Errorf("incorrect number of ExtKeyUsage: expected %v, got %v", expected, got) - } - if expected, got := x509.ExtKeyUsageServerAuth, cert.ExtKeyUsage[0]; got != expected { - return fmt.Errorf("incorrect ExtKeyUsage[0]: expected %v, got %v", expected, got) - } - if expected, got := x509.ExtKeyUsageClientAuth, cert.ExtKeyUsage[1]; got != expected { - return fmt.Errorf("incorrect ExtKeyUsage[1]: expected %v, got %v", expected, got) - } - - if expected, got := x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature, cert.KeyUsage; got != expected { - return fmt.Errorf("incorrect KeyUsage: expected %v, got %v", expected, got) - } - - // This time checking is a bit sloppy to avoid inconsistent test results - // depending on the power of the machine running the tests. - now := time.Now() - if cert.NotBefore.After(now) { - return fmt.Errorf("certificate validity begins in the future") - } - if now.Sub(cert.NotBefore) > (2 * time.Minute) { - return fmt.Errorf("certificate validity begins more than two minutes in the past") - } - if cert.NotAfter.Sub(cert.NotBefore) != time.Hour { - return fmt.Errorf("certificate validity is not one hour") - } - - caBlock, _ := pem.Decode([]byte(testCACert)) - caCert, err := x509.ParseCertificate(caBlock.Bytes) - if err != nil { - return fmt.Errorf("error parsing ca cert: %s", err) - } - certPool := x509.NewCertPool() - - // Verify certificate - _, err = cert.Verify(x509.VerifyOptions{Roots: certPool}) - if err == nil { - return fmt.Errorf("incorrectly verified certificate") - } else if _, ok := err.(x509.UnknownAuthorityError); !ok { - return fmt.Errorf("incorrect verify error: expected UnknownAuthorityError, got %v", err) - } - certPool.AddCert(caCert) - if _, err = cert.Verify(x509.VerifyOptions{Roots: certPool}); err != nil { - return fmt.Errorf("verify failed: %s", err) - } - - return nil - }, + Config: locallySignedCertConfig(1, 0), + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_locally_signed_cert.test", "cert_pem", PreambleCertificate), + testCheckPEMCertificateSubject("tls_locally_signed_cert.test", "cert_pem", &pkix.Name{ + SerialNumber: "2", + CommonName: "example.com", + Organization: []string{"Example, Inc"}, + OrganizationalUnit: []string{"Department of Terraform Testing"}, + StreetAddress: []string{"5879 Cotton Link"}, + Locality: []string{"Pirate Harbor"}, + Province: []string{"CA"}, + Country: []string{"US"}, + PostalCode: []string{"95559-1227"}, + }), + testCheckPEMCertificateDNSNames("tls_locally_signed_cert.test", "cert_pem", []string{ + "example.com", + "example.net", + }), + testCheckPEMCertificateIPAddresses("tls_locally_signed_cert.test", "cert_pem", []net.IP{ + net.ParseIP("127.0.0.1"), + net.ParseIP("127.0.0.2"), + }), + testCheckPEMCertificateURIs("tls_locally_signed_cert.test", "cert_pem", []*url.URL{ + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "workload", + }, + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "workload2", + }, + }), + testCheckPEMCertificateKeyUsage("tls_locally_signed_cert.test", "cert_pem", x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature), + testCheckPEMCertificateExtKeyUsages("tls_locally_signed_cert.test", "cert_pem", []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + testCheckPEMCertificateAgainstPEMRootCA("tls_locally_signed_cert.test", "cert_pem", []byte(testCACert)), + testCheckPEMCertificateDuration("tls_locally_signed_cert.test", "cert_pem", time.Hour), + ), }, }, }) @@ -151,69 +72,43 @@ func TestAccLocallySignedCertRecreatesAfterExpired(t *testing.T) { PreCheck: setTimeForTest("2019-06-14T12:00:00Z"), Steps: []r.TestStep{ { - Config: locallySignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - previousCert = got + Config: locallySignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_locally_signed_cert.test", "cert_pem", func(value string) error { + previousCert = value return nil - }, + }), }, { - Config: locallySignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - - if got != previousCert { + Config: locallySignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_locally_signed_cert.test", "cert_pem", func(value string) error { + if value != previousCert { return fmt.Errorf("certificate updated even though no time has passed") } - - previousCert = got + previousCert = value return nil - }, + }), }, { PreConfig: setTimeForTest("2019-06-14T19:00:00Z"), - Config: locallySignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - - if got != previousCert { + Config: locallySignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_locally_signed_cert.test", "cert_pem", func(value string) error { + if value != previousCert { return fmt.Errorf("certificate updated even though not enough time has passed") } - - previousCert = got + previousCert = value return nil - }, + }), }, { PreConfig: setTimeForTest("2019-06-14T21:00:00Z"), - Config: locallySignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - - if got == previousCert { + Config: locallySignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_locally_signed_cert.test", "cert_pem", func(value string) error { + if value == previousCert { return fmt.Errorf("certificate not updated even though passed early renewal") } - - previousCert = got + previousCert = value return nil - }, + }), }, }, }) @@ -228,69 +123,43 @@ func TestAccLocallySignedCertNotRecreatedForEarlyRenewalUpdateInFuture(t *testin PreCheck: setTimeForTest("2019-06-14T12:00:00Z"), Steps: []r.TestStep{ { - Config: locallySignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - previousCert = got + Config: locallySignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_locally_signed_cert.test", "cert_pem", func(value string) error { + previousCert = value return nil - }, + }), }, { - Config: locallySignedCertConfig(10, 3, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - - if got != previousCert { + Config: locallySignedCertConfig(10, 3), + Check: r.TestCheckResourceAttrWith("tls_locally_signed_cert.test", "cert_pem", func(value string) error { + if value != previousCert { return fmt.Errorf("certificate updated even though still time until early renewal") } - - previousCert = got + previousCert = value return nil - }, + }), }, { PreConfig: setTimeForTest("2019-06-14T16:00:00Z"), - Config: locallySignedCertConfig(10, 3, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - - if got != previousCert { + Config: locallySignedCertConfig(10, 3), + Check: r.TestCheckResourceAttrWith("tls_locally_signed_cert.test", "cert_pem", func(value string) error { + if value != previousCert { return fmt.Errorf("certificate updated even though still time until early renewal") } - - previousCert = got + previousCert = value return nil - }, + }), }, { PreConfig: setTimeForTest("2019-06-14T16:00:00Z"), - Config: locallySignedCertConfig(10, 9, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - - if got == previousCert { + Config: locallySignedCertConfig(10, 9), + Check: r.TestCheckResourceAttrWith("tls_locally_signed_cert.test", "cert_pem", func(value string) error { + if value == previousCert { return fmt.Errorf("certificate not updated even though early renewal time has passed") } - - previousCert = got + previousCert = value return nil - }, + }), }, }, }) @@ -303,165 +172,98 @@ func TestAccLocallySignedCert_HandleKeyAlgorithmDeprecation(t *testing.T) { ProviderFactories: testProviders, Steps: []r.TestStep{ { - Config: locallySignedCertConfig(1, 0, true), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["cert_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"cert_pem\" is not a string") - } - if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") { - return fmt.Errorf("key is missing cert PEM preamble") - } - block, _ := pem.Decode([]byte(got)) - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("error parsing cert: %s", err) - } - if expected, got := "2", cert.Subject.SerialNumber; got != expected { - return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected, got) - } - if expected, got := "example.com", cert.Subject.CommonName; got != expected { - return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected, got) - } - if expected, got := "Example, Inc", cert.Subject.Organization[0]; got != expected { - return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected, got) - } - if expected, got := "Department of Terraform Testing", cert.Subject.OrganizationalUnit[0]; got != expected { - return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected, got) - } - if expected, got := "5879 Cotton Link", cert.Subject.StreetAddress[0]; got != expected { - return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected, got) - } - if expected, got := "Pirate Harbor", cert.Subject.Locality[0]; got != expected { - return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected, got) - } - if expected, got := "CA", cert.Subject.Province[0]; got != expected { - return fmt.Errorf("incorrect subject province: expected %v, got %v", expected, got) - } - if expected, got := "US", cert.Subject.Country[0]; got != expected { - return fmt.Errorf("incorrect subject country: expected %v, got %v", expected, got) - } - if expected, got := "95559-1227", cert.Subject.PostalCode[0]; got != expected { - return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.DNSNames); got != expected { - return fmt.Errorf("incorrect number of DNS names: expected %v, got %v", expected, got) - } - if expected, got := "example.com", cert.DNSNames[0]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - if expected, got := "example.net", cert.DNSNames[1]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.IPAddresses); got != expected { - return fmt.Errorf("incorrect number of IP addresses: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.1", cert.IPAddresses[0].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.2", cert.IPAddresses[1].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - if expected, got := 2, len(cert.URIs); got != expected { - return fmt.Errorf("incorrect number of URIs: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/workload", cert.URIs[0].String(); got != expected { - return fmt.Errorf("incorrect URI 0: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/workload2", cert.URIs[1].String(); got != expected { - return fmt.Errorf("incorrect URI 1: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.ExtKeyUsage); got != expected { - return fmt.Errorf("incorrect number of ExtKeyUsage: expected %v, got %v", expected, got) - } - if expected, got := x509.ExtKeyUsageServerAuth, cert.ExtKeyUsage[0]; got != expected { - return fmt.Errorf("incorrect ExtKeyUsage[0]: expected %v, got %v", expected, got) - } - if expected, got := x509.ExtKeyUsageClientAuth, cert.ExtKeyUsage[1]; got != expected { - return fmt.Errorf("incorrect ExtKeyUsage[1]: expected %v, got %v", expected, got) - } - - if expected, got := x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature, cert.KeyUsage; got != expected { - return fmt.Errorf("incorrect KeyUsage: expected %v, got %v", expected, got) - } - - // This time checking is a bit sloppy to avoid inconsistent test results - // depending on the power of the machine running the tests. - now := time.Now() - if cert.NotBefore.After(now) { - return fmt.Errorf("certificate validity begins in the future") - } - if now.Sub(cert.NotBefore) > (2 * time.Minute) { - return fmt.Errorf("certificate validity begins more than two minutes in the past") - } - if cert.NotAfter.Sub(cert.NotBefore) != time.Hour { - return fmt.Errorf("certificate validity is not one hour") - } - - caBlock, _ := pem.Decode([]byte(testCACert)) - caCert, err := x509.ParseCertificate(caBlock.Bytes) - if err != nil { - return fmt.Errorf("error parsing ca cert: %s", err) - } - certPool := x509.NewCertPool() - - // Verify certificate - _, err = cert.Verify(x509.VerifyOptions{Roots: certPool}) - if err == nil { - return fmt.Errorf("incorrectly verified certificate") - } else if _, ok := err.(x509.UnknownAuthorityError); !ok { - return fmt.Errorf("incorrect verify error: expected UnknownAuthorityError, got %v", err) - } - certPool.AddCert(caCert) - if _, err = cert.Verify(x509.VerifyOptions{Roots: certPool}); err != nil { - return fmt.Errorf("verify failed: %s", err) - } - - return nil - }, + Config: locallySignedCertConfigWithDeprecatedKeyAlgorithm(1, 0), + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_locally_signed_cert.test", "cert_pem", PreambleCertificate), + testCheckPEMCertificateSubject("tls_locally_signed_cert.test", "cert_pem", &pkix.Name{ + SerialNumber: "2", + CommonName: "example.com", + Organization: []string{"Example, Inc"}, + OrganizationalUnit: []string{"Department of Terraform Testing"}, + StreetAddress: []string{"5879 Cotton Link"}, + Locality: []string{"Pirate Harbor"}, + Province: []string{"CA"}, + Country: []string{"US"}, + PostalCode: []string{"95559-1227"}, + }), + testCheckPEMCertificateDNSNames("tls_locally_signed_cert.test", "cert_pem", []string{ + "example.com", + "example.net", + }), + testCheckPEMCertificateIPAddresses("tls_locally_signed_cert.test", "cert_pem", []net.IP{ + net.ParseIP("127.0.0.1"), + net.ParseIP("127.0.0.2"), + }), + testCheckPEMCertificateURIs("tls_locally_signed_cert.test", "cert_pem", []*url.URL{ + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "workload", + }, + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "workload2", + }, + }), + testCheckPEMCertificateKeyUsage("tls_locally_signed_cert.test", "cert_pem", x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature), + testCheckPEMCertificateExtKeyUsages("tls_locally_signed_cert.test", "cert_pem", []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + testCheckPEMCertificateAgainstPEMRootCA("tls_locally_signed_cert.test", "cert_pem", []byte(testCACert)), + testCheckPEMCertificateDuration("tls_locally_signed_cert.test", "cert_pem", time.Hour), + ), }, }, }) } -func locallySignedCertConfig(validity, earlyRenewal uint32, setKeyAlgorithm bool) string { - caKeyAlgorithmCfg := "" - if setKeyAlgorithm { - caKeyAlgorithmCfg = `ca_key_algorithm = "RSA"` - } - +func locallySignedCertConfig(validity, earlyRenewal uint32) string { return fmt.Sprintf(` - resource "tls_locally_signed_cert" "test" { - cert_request_pem = < 1700 { - return fmt.Errorf("private key PEM looks too long for a 2048-bit key (got %v characters)", len(gotPrivate)) - } - - // Check `.public_key_pem` - gotPublicUntyped := s.RootModule().Outputs["public_key_pem"].Value - gotPublic, ok := gotPublicUntyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_pem\" is not a string") - } - if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") { - return fmt.Errorf("public key is missing public key PEM preamble") - } - - // Check `.public_key_openssh` - gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"].Value - gotPublicSSH, ok := gotPublicSSHUntyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_openssh\" is not a string") - } - if !strings.HasPrefix(gotPublicSSH, "ssh-rsa ") { - return fmt.Errorf("SSH public key is missing ssh-rsa prefix") - } - - // Check `.private_key_openssh` - gotPrivateSSHUntyped := s.RootModule().Outputs["private_key_openssh"].Value - gotPrivateSSH, ok := gotPrivateSSHUntyped.(string) - if !ok { - return fmt.Errorf("output for \"private_key_openssh\" is not a string") - } - if !strings.HasPrefix(gotPrivateSSH, "-----BEGIN OPENSSH PRIVATE KEY----") { - return fmt.Errorf("private key is missing RSA key OPENSSH PEM preamble") - } - - // Check `.public_key_fingerprint_md5` - gotPublicFingerprintMD5Untyped := s.RootModule().Outputs["public_key_fingerprint_md5"].Value - gotPublicFingerprintMD5, ok := gotPublicFingerprintMD5Untyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_fingerprint_md5\" is not a string") - } - if gotPublicFingerprintMD5[2] != ':' { - return fmt.Errorf("MD5 public key fingerprint is missing ':' in the correct place") - } - - // Check `.public_key_fingerprint_sha256` - gotPublicFingerprintSHA256Untyped := s.RootModule().Outputs["public_key_fingerprint_sha256"].Value - gotPublicFingerprintSHA256, ok := gotPublicFingerprintSHA256Untyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_fingerprint_sha256\" is not a string") - } - if !(strings.HasPrefix(gotPublicFingerprintSHA256, "SHA256:")) { - return fmt.Errorf("SHA256 public key fingerprint is is missing the expected preamble") - } - - return nil - }, + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_private_key.test", "private_key_pem", PreamblePrivateKeyRSA), + r.TestCheckResourceAttrWith("tls_private_key.test", "private_key_pem", func(pem string) error { + if len(pem) > 1700 { + return fmt.Errorf("private key PEM looks too long for a 2048-bit key (got %v characters)", len(pem)) + } + return nil + }), + testCheckPEMFormat("tls_private_key.test", "public_key_pem", PreamblePublicKey), + testCheckPEMFormat("tls_private_key.test", "private_key_openssh", PreamblePrivateKeyOpenSSH), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_openssh", regexp.MustCompile(`^ssh-rsa `)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_fingerprint_md5", regexp.MustCompile(`^([abcdef\d]{2}:){15}[abcdef\d]{2}`)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^SHA256:`)), + ), }, { Config: ` @@ -112,25 +39,16 @@ func TestPrivateKeyRSA(t *testing.T) { algorithm = "RSA" rsa_bits = 4096 } - output "key_pem" { - value = "${tls_private_key.test.private_key_pem}" - sensitive = true - } `, - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem\" is not a string") - } - if !strings.HasPrefix(got, "-----BEGIN RSA PRIVATE KEY----") { - return fmt.Errorf("key is missing RSA key PEM preamble") - } - if len(got) < 1700 { - return fmt.Errorf("key PEM looks too short for a 4096-bit key (got %v characters)", len(got)) - } - return nil - }, + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_private_key.test", "private_key_pem", PreamblePrivateKeyRSA), + r.TestCheckResourceAttrWith("tls_private_key.test", "private_key_pem", func(pem string) error { + if len(pem) < 1700 { + return fmt.Errorf("private key PEM looks too short for a 4096-bit key (got %v characters)", len(pem)) + } + return nil + }), + ), }, }, }) @@ -145,90 +63,15 @@ func TestPrivateKeyECDSA(t *testing.T) { resource "tls_private_key" "test" { algorithm = "ECDSA" } - output "private_key_pem" { - value = "${tls_private_key.test.private_key_pem}" - sensitive = true - } - output "private_key_openssh" { - value = "${tls_private_key.test.private_key_openssh}" - sensitive = true - } - output "public_key_pem" { - value = "${tls_private_key.test.public_key_pem}" - } - output "public_key_openssh" { - value = "${tls_private_key.test.public_key_openssh}" - } - output "public_key_fingerprint_md5" { - value = "${tls_private_key.test.public_key_fingerprint_md5}" - } - output "public_key_fingerprint_sha256" { - value = "${tls_private_key.test.public_key_fingerprint_sha256}" - } `, - Check: func(s *terraform.State) error { - // Check `.private_key_pem` - gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"].Value - gotPrivate, ok := gotPrivateUntyped.(string) - if !ok { - return fmt.Errorf("output for \"private_key_pem\" is not a string") - } - if !strings.HasPrefix(gotPrivate, "-----BEGIN EC PRIVATE KEY----") { - return fmt.Errorf("private key is missing EC key PEM preamble") - } - - // Check `.public_key_pem` - gotPublicUntyped := s.RootModule().Outputs["public_key_pem"].Value - gotPublic, ok := gotPublicUntyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_pem\" is not a string") - } - if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") { - return fmt.Errorf("public key is missing public key PEM preamble") - } - - // Check `.public_key_openssh` - gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"].Value - gotPublicSSH, ok := gotPublicSSHUntyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_openssh\" is not a string") - } - if gotPublicSSH != "" { - return fmt.Errorf("SSH public key should not be set for ECDSA P-224 key") - } - - // Check `.private_key_openssh` - gotPrivateSSHUntyped := s.RootModule().Outputs["private_key_openssh"].Value - gotPrivateSSH, ok := gotPrivateSSHUntyped.(string) - if !ok { - return fmt.Errorf("output for \"private_key_openssh\" is not a string") - } - if gotPrivateSSH != "" { - return fmt.Errorf("SSH private key should not be set for ECDSA P-224 key") - } - - // Check `.public_key_fingerprint_md5` - gotPublicFingerprintMD5Untyped := s.RootModule().Outputs["public_key_fingerprint_md5"].Value - gotPublicFingerprintMD5, ok := gotPublicFingerprintMD5Untyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_fingerprint_md5\" is not a string") - } - if gotPublicFingerprintMD5 != "" { - return fmt.Errorf("MD5 public key fingerprint should not be set for ECDSA P-224 key") - } - - // Check `.public_key_fingerprint_sha256` - gotPublicFingerprintSHA256Untyped := s.RootModule().Outputs["public_key_fingerprint_sha256"].Value - gotPublicFingerprintSHA256, ok := gotPublicFingerprintSHA256Untyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_fingerprint_sha256\" is not a string") - } - if gotPublicFingerprintSHA256 != "" { - return fmt.Errorf("SHA256 public key fingerprint should not be st for ECDSA P-224 key") - } - - return nil - }, + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_private_key.test", "private_key_pem", PreamblePrivateKeyEC), + testCheckPEMFormat("tls_private_key.test", "public_key_pem", PreamblePublicKey), + r.TestCheckResourceAttr("tls_private_key.test", "private_key_openssh", ""), + r.TestCheckResourceAttr("tls_private_key.test", "public_key_openssh", ""), + r.TestCheckResourceAttr("tls_private_key.test", "public_key_fingerprint_md5", ""), + r.TestCheckResourceAttr("tls_private_key.test", "public_key_fingerprint_sha256", ""), + ), }, { Config: ` @@ -236,90 +79,15 @@ func TestPrivateKeyECDSA(t *testing.T) { algorithm = "ECDSA" ecdsa_curve = "P256" } - output "private_key_pem" { - value = "${tls_private_key.test.private_key_pem}" - sensitive = true - } - output "private_key_openssh" { - value = "${tls_private_key.test.private_key_openssh}" - sensitive = true - } - output "public_key_pem" { - value = "${tls_private_key.test.public_key_pem}" - } - output "public_key_openssh" { - value = "${tls_private_key.test.public_key_openssh}" - } - output "public_key_fingerprint_md5" { - value = "${tls_private_key.test.public_key_fingerprint_md5}" - } - output "public_key_fingerprint_sha256" { - value = "${tls_private_key.test.public_key_fingerprint_sha256}" - } `, - Check: func(s *terraform.State) error { - // Check `.private_key_pem` - gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"].Value - gotPrivate, ok := gotPrivateUntyped.(string) - if !ok { - return fmt.Errorf("output for \"private_key_pem\" is not a string") - } - if !strings.HasPrefix(gotPrivate, "-----BEGIN EC PRIVATE KEY----") { - return fmt.Errorf("private key is missing EC key PEM preamble") - } - - // Check `.public_key_pem` - gotPublicUntyped := s.RootModule().Outputs["public_key_pem"].Value - gotPublic, ok := gotPublicUntyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_pem\" is not a string") - } - if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") { - return fmt.Errorf("public key is missing public key PEM preamble") - } - - // Check `.public_key_openssh` - gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"].Value - gotPublicSSH, ok := gotPublicSSHUntyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_openssh\" is not a string") - } - if !strings.HasPrefix(gotPublicSSH, "ecdsa-sha2-nistp256 ") { - return fmt.Errorf("SSH public key is missing ecdsa-sha2-nistp256 prefix") - } - - // Check `.private_key_openssh` - gotPrivateSSHUntyped := s.RootModule().Outputs["private_key_openssh"].Value - gotPrivateSSH, ok := gotPrivateSSHUntyped.(string) - if !ok { - return fmt.Errorf("output for \"private_key_openssh\" is not a string") - } - if !strings.HasPrefix(gotPrivateSSH, "-----BEGIN OPENSSH PRIVATE KEY----") { - return fmt.Errorf("private key is missing RSA key OPENSSH PEM preamble") - } - - // Check `.public_key_fingerprint_md5` - gotPublicFingerprintMD5Untyped := s.RootModule().Outputs["public_key_fingerprint_md5"].Value - gotPublicFingerprintMD5, ok := gotPublicFingerprintMD5Untyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_fingerprint_md5\" is not a string") - } - if gotPublicFingerprintMD5[2] != ':' { - return fmt.Errorf("MD5 public key fingerprint is missing ':' in the correct place") - } - - // Check `.public_key_fingerprint_sha256` - gotPublicFingerprintSHA256Untyped := s.RootModule().Outputs["public_key_fingerprint_sha256"].Value - gotPublicFingerprintSHA256, ok := gotPublicFingerprintSHA256Untyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_fingerprint_sha256\" is not a string") - } - if !(strings.HasPrefix(gotPublicFingerprintSHA256, "SHA256:")) { - return fmt.Errorf("SHA256 public key fingerprint is is missing the expected preamble") - } - - return nil - }, + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_private_key.test", "private_key_pem", PreamblePrivateKeyEC), + testCheckPEMFormat("tls_private_key.test", "public_key_pem", PreamblePublicKey), + testCheckPEMFormat("tls_private_key.test", "private_key_openssh", PreamblePrivateKeyOpenSSH), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_openssh", regexp.MustCompile(`^ecdsa-sha2-nistp256 `)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_fingerprint_md5", regexp.MustCompile(`^([abcdef\d]{2}:){15}[abcdef\d]{2}`)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^SHA256:`)), + ), }, }, }) @@ -334,90 +102,15 @@ func TestPrivateKeyED25519(t *testing.T) { resource "tls_private_key" "test" { algorithm = "ED25519" } - output "private_key_pem" { - value = "${tls_private_key.test.private_key_pem}" - sensitive = true - } - output "private_key_openssh" { - value = "${tls_private_key.test.private_key_openssh}" - sensitive = true - } - output "public_key_pem" { - value = "${tls_private_key.test.public_key_pem}" - } - output "public_key_openssh" { - value = "${tls_private_key.test.public_key_openssh}" - } - output "public_key_fingerprint_md5" { - value = "${tls_private_key.test.public_key_fingerprint_md5}" - } - output "public_key_fingerprint_sha256" { - value = "${tls_private_key.test.public_key_fingerprint_sha256}" - } `, - Check: func(s *terraform.State) error { - // Check `.private_key_pem` - gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"].Value - gotPrivate, ok := gotPrivateUntyped.(string) - if !ok { - return fmt.Errorf("output for \"private_key_pem\" is not a string") - } - if !strings.HasPrefix(gotPrivate, "-----BEGIN PRIVATE KEY----") { - return fmt.Errorf("private key is missing ED25519 key PEM preamble") - } - - // Check `.public_key_pem` - gotPublicUntyped := s.RootModule().Outputs["public_key_pem"].Value - gotPublic, ok := gotPublicUntyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_pem\" is not a string") - } - if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") { - return fmt.Errorf("public key is missing public key PEM preamble") - } - - // Check `.public_key_openssh` - gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"].Value - gotPublicSSH, ok := gotPublicSSHUntyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_openssh\" is not a string") - } - if !strings.HasPrefix(gotPublicSSH, "ssh-ed25519 ") { - return fmt.Errorf("SSH public key is missing sh-ed25519 prefix") - } - - // Check `.private_key_openssh` - gotPrivateSSHUntyped := s.RootModule().Outputs["private_key_openssh"].Value - gotPrivateSSH, ok := gotPrivateSSHUntyped.(string) - if !ok { - return fmt.Errorf("output for \"private_key_openssh\" is not a string") - } - if !strings.HasPrefix(gotPrivateSSH, "-----BEGIN OPENSSH PRIVATE KEY----") { - return fmt.Errorf("private key is missing RSA key OPENSSH PEM preamble") - } - - // Check `.public_key_fingerprint_md5` - gotPublicFingerprintMD5Untyped := s.RootModule().Outputs["public_key_fingerprint_md5"].Value - gotPublicFingerprintMD5, ok := gotPublicFingerprintMD5Untyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_fingerprint_md5\" is not a string") - } - if gotPublicFingerprintMD5[2] != ':' { - return fmt.Errorf("MD5 public key fingerprint is missing ':' in the correct place") - } - - // Check `.public_key_fingerprint_sha256` - gotPublicFingerprintSHA256Untyped := s.RootModule().Outputs["public_key_fingerprint_sha256"].Value - gotPublicFingerprintSHA256, ok := gotPublicFingerprintSHA256Untyped.(string) - if !ok { - return fmt.Errorf("output for \"public_key_fingerprint_sha256\" is not a string") - } - if !(strings.HasPrefix(gotPublicFingerprintSHA256, "SHA256:")) { - return fmt.Errorf("SHA256 public key fingerprint is is missing the expected preamble") - } - - return nil - }, + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_private_key.test", "private_key_pem", PreamblePrivateKeyPKCS8), + testCheckPEMFormat("tls_private_key.test", "public_key_pem", PreamblePublicKey), + testCheckPEMFormat("tls_private_key.test", "private_key_openssh", PreamblePrivateKeyOpenSSH), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_openssh", regexp.MustCompile(`^ssh-ed25519 `)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_fingerprint_md5", regexp.MustCompile(`^([abcdef\d]{2}:){15}[abcdef\d]{2}`)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^SHA256:`)), + ), }, }, }) diff --git a/internal/provider/resource_self_signed_cert_test.go b/internal/provider/resource_self_signed_cert_test.go index c70140c1..12a1d07b 100644 --- a/internal/provider/resource_self_signed_cert_test.go +++ b/internal/provider/resource_self_signed_cert_test.go @@ -3,15 +3,15 @@ package provider import ( "bytes" "crypto/x509" - "encoding/pem" + "crypto/x509/pkix" "fmt" + "net" + "net/url" "regexp" - "strings" "testing" "time" r "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestSelfSignedCert(t *testing.T) { @@ -19,109 +19,47 @@ func TestSelfSignedCert(t *testing.T) { ProviderFactories: testProviders, Steps: []r.TestStep{ { - Config: selfSignedCertConfig(1, 0, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") { - return fmt.Errorf("key is missing cert PEM preamble") - } - block, _ := pem.Decode([]byte(got)) - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("error parsing cert: %s", err) - } - if expected, got := "2", cert.Subject.SerialNumber; got != expected { - return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected, got) - } - if expected, got := "example.com", cert.Subject.CommonName; got != expected { - return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected, got) - } - if expected, got := "Example, Inc", cert.Subject.Organization[0]; got != expected { - return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected, got) - } - if expected, got := "Department of Terraform Testing", cert.Subject.OrganizationalUnit[0]; got != expected { - return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected, got) - } - if expected, got := "5879 Cotton Link", cert.Subject.StreetAddress[0]; got != expected { - return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected, got) - } - if expected, got := "Pirate Harbor", cert.Subject.Locality[0]; got != expected { - return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected, got) - } - if expected, got := "CA", cert.Subject.Province[0]; got != expected { - return fmt.Errorf("incorrect subject province: expected %v, got %v", expected, got) - } - if expected, got := "US", cert.Subject.Country[0]; got != expected { - return fmt.Errorf("incorrect subject country: expected %v, got %v", expected, got) - } - if expected, got := "95559-1227", cert.Subject.PostalCode[0]; got != expected { - return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.DNSNames); got != expected { - return fmt.Errorf("incorrect number of DNS names: expected %v, got %v", expected, got) - } - if expected, got := "example.com", cert.DNSNames[0]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - if expected, got := "example.net", cert.DNSNames[1]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.IPAddresses); got != expected { - return fmt.Errorf("incorrect number of IP addresses: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.1", cert.IPAddresses[0].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.2", cert.IPAddresses[1].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.URIs); got != expected { - return fmt.Errorf("incorrect number of URIs: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/ca", cert.URIs[0].String(); got != expected { - return fmt.Errorf("incorrect URI 0: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/ca2", cert.URIs[1].String(); got != expected { - return fmt.Errorf("incorrect URI 1: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.ExtKeyUsage); got != expected { - return fmt.Errorf("incorrect number of ExtKeyUsage: expected %v, got %v", expected, got) - } - if expected, got := x509.ExtKeyUsageServerAuth, cert.ExtKeyUsage[0]; got != expected { - return fmt.Errorf("incorrect ExtKeyUsage[0]: expected %v, got %v", expected, got) - } - if expected, got := x509.ExtKeyUsageClientAuth, cert.ExtKeyUsage[1]; got != expected { - return fmt.Errorf("incorrect ExtKeyUsage[1]: expected %v, got %v", expected, got) - } - - if expected, got := x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature, cert.KeyUsage; got != expected { - return fmt.Errorf("incorrect KeyUsage: expected %v, got %v", expected, got) - } - - // This time checking is a bit sloppy to avoid inconsistent test results - // depending on the power of the machine running the tests. - now := time.Now() - if cert.NotBefore.After(now) { - return fmt.Errorf("certificate validity begins in the future") - } - if now.Sub(cert.NotBefore) > (2 * time.Minute) { - return fmt.Errorf("certificate validity begins more than two minutes in the past") - } - if cert.NotAfter.Sub(cert.NotBefore) != time.Hour { - return fmt.Errorf("certificate validity is not one hour") - } - - return nil - }, + Config: selfSignedCertConfig(1, 0), + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_self_signed_cert.test1", "cert_pem", PreambleCertificate), + testCheckPEMCertificateSubject("tls_self_signed_cert.test1", "cert_pem", &pkix.Name{ + SerialNumber: "2", + CommonName: "example.com", + Organization: []string{"Example, Inc"}, + OrganizationalUnit: []string{"Department of Terraform Testing"}, + StreetAddress: []string{"5879 Cotton Link"}, + Locality: []string{"Pirate Harbor"}, + Province: []string{"CA"}, + Country: []string{"US"}, + PostalCode: []string{"95559-1227"}, + }), + testCheckPEMCertificateDNSNames("tls_self_signed_cert.test1", "cert_pem", []string{ + "example.com", + "example.net", + }), + testCheckPEMCertificateIPAddresses("tls_self_signed_cert.test1", "cert_pem", []net.IP{ + net.ParseIP("127.0.0.1"), + net.ParseIP("127.0.0.2"), + }), + testCheckPEMCertificateURIs("tls_self_signed_cert.test1", "cert_pem", []*url.URL{ + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "ca", + }, + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "ca2", + }, + }), + testCheckPEMCertificateKeyUsage("tls_self_signed_cert.test1", "cert_pem", x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature), + testCheckPEMCertificateExtKeyUsages("tls_self_signed_cert.test1", "cert_pem", []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + testCheckPEMCertificateDuration("tls_self_signed_cert.test1", "cert_pem", time.Hour), + ), }, { Config: fmt.Sprintf(` @@ -135,74 +73,18 @@ func TestSelfSignedCert(t *testing.T) { %s EOT } - output "key_pem_2" { - value = tls_self_signed_cert.test2.cert_pem - } `, testPrivateKeyPEM), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_2"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_2\" is not a string") - } - - if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") { - return fmt.Errorf("key is missing cert PEM preamble") - } - block, _ := pem.Decode([]byte(got)) - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("error parsing cert: %s", err) - } - if expected, got := "42", cert.Subject.SerialNumber; got != expected { - return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected, got) - } - if expected, got := "", cert.Subject.CommonName; got != expected { - return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected, got) - } - if expected, got := 0, len(cert.Subject.Organization); got != expected { - return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected, got) - } - if expected, got := 0, len(cert.Subject.OrganizationalUnit); got != expected { - return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected, got) - } - if expected, got := 0, len(cert.Subject.StreetAddress); got != expected { - return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected, got) - } - if expected, got := 0, len(cert.Subject.Locality); got != expected { - return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected, got) - } - if expected, got := 0, len(cert.Subject.Province); got != expected { - return fmt.Errorf("incorrect subject province: expected %v, got %v", expected, got) - } - if expected, got := 0, len(cert.Subject.Country); got != expected { - return fmt.Errorf("incorrect subject country: expected %v, got %v", expected, got) - } - if expected, got := 0, len(cert.Subject.PostalCode); got != expected { - return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected, got) - } - - if expected, got := 0, len(cert.DNSNames); got != expected { - return fmt.Errorf("incorrect number of DNS names: expected %v, got %v", expected, got) - } - - if expected, got := 0, len(cert.IPAddresses); got != expected { - return fmt.Errorf("incorrect number of IP addresses: expected %v, got %v", expected, got) - } - - if expected, got := 0, len(cert.ExtKeyUsage); got != expected { - return fmt.Errorf("incorrect number of ExtKeyUsage: expected %v, got %v", expected, got) - } - if expected, got := []byte(``), cert.SubjectKeyId; !bytes.Equal(got, expected) { - return fmt.Errorf("incorrect subject key id: expected %v, got %v", expected, got) - } - - if expected, got := x509.KeyUsage(0), cert.KeyUsage; got != expected { - return fmt.Errorf("incorrect KeyUsage: expected %v, got %v", expected, got) - } - - return nil - }, + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_self_signed_cert.test2", "cert_pem", PreambleCertificate), + testCheckPEMCertificateSubject("tls_self_signed_cert.test2", "cert_pem", &pkix.Name{ + SerialNumber: "42", + }), + testCheckPEMCertificateDNSNames("tls_self_signed_cert.test2", "cert_pem", []string{}), + testCheckPEMCertificateIPAddresses("tls_self_signed_cert.test2", "cert_pem", []net.IP{}), + testCheckPEMCertificateURIs("tls_self_signed_cert.test2", "cert_pem", []*url.URL{}), + testCheckPEMCertificateKeyUsage("tls_self_signed_cert.test2", "cert_pem", x509.KeyUsage(0)), + testCheckPEMCertificateExtKeyUsages("tls_self_signed_cert.test2", "cert_pem", []x509.ExtKeyUsage{}), + ), }, }, }) @@ -214,109 +96,47 @@ func TestSelfSignedCert_HandleKeyAlgorithmDeprecation(t *testing.T) { ProviderFactories: testProviders, Steps: []r.TestStep{ { - Config: selfSignedCertConfig(1, 0, true), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") { - return fmt.Errorf("key is missing cert PEM preamble") - } - block, _ := pem.Decode([]byte(got)) - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return fmt.Errorf("error parsing cert: %s", err) - } - if expected, got := "2", cert.Subject.SerialNumber; got != expected { - return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected, got) - } - if expected, got := "example.com", cert.Subject.CommonName; got != expected { - return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected, got) - } - if expected, got := "Example, Inc", cert.Subject.Organization[0]; got != expected { - return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected, got) - } - if expected, got := "Department of Terraform Testing", cert.Subject.OrganizationalUnit[0]; got != expected { - return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected, got) - } - if expected, got := "5879 Cotton Link", cert.Subject.StreetAddress[0]; got != expected { - return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected, got) - } - if expected, got := "Pirate Harbor", cert.Subject.Locality[0]; got != expected { - return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected, got) - } - if expected, got := "CA", cert.Subject.Province[0]; got != expected { - return fmt.Errorf("incorrect subject province: expected %v, got %v", expected, got) - } - if expected, got := "US", cert.Subject.Country[0]; got != expected { - return fmt.Errorf("incorrect subject country: expected %v, got %v", expected, got) - } - if expected, got := "95559-1227", cert.Subject.PostalCode[0]; got != expected { - return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.DNSNames); got != expected { - return fmt.Errorf("incorrect number of DNS names: expected %v, got %v", expected, got) - } - if expected, got := "example.com", cert.DNSNames[0]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - if expected, got := "example.net", cert.DNSNames[1]; got != expected { - return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.IPAddresses); got != expected { - return fmt.Errorf("incorrect number of IP addresses: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.1", cert.IPAddresses[0].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - if expected, got := "127.0.0.2", cert.IPAddresses[1].String(); got != expected { - return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.URIs); got != expected { - return fmt.Errorf("incorrect number of URIs: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/ca", cert.URIs[0].String(); got != expected { - return fmt.Errorf("incorrect URI 0: expected %v, got %v", expected, got) - } - if expected, got := "spiffe://example-trust-domain/ca2", cert.URIs[1].String(); got != expected { - return fmt.Errorf("incorrect URI 1: expected %v, got %v", expected, got) - } - - if expected, got := 2, len(cert.ExtKeyUsage); got != expected { - return fmt.Errorf("incorrect number of ExtKeyUsage: expected %v, got %v", expected, got) - } - if expected, got := x509.ExtKeyUsageServerAuth, cert.ExtKeyUsage[0]; got != expected { - return fmt.Errorf("incorrect ExtKeyUsage[0]: expected %v, got %v", expected, got) - } - if expected, got := x509.ExtKeyUsageClientAuth, cert.ExtKeyUsage[1]; got != expected { - return fmt.Errorf("incorrect ExtKeyUsage[1]: expected %v, got %v", expected, got) - } - - if expected, got := x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature, cert.KeyUsage; got != expected { - return fmt.Errorf("incorrect KeyUsage: expected %v, got %v", expected, got) - } - - // This time checking is a bit sloppy to avoid inconsistent test results - // depending on the power of the machine running the tests. - now := time.Now() - if cert.NotBefore.After(now) { - return fmt.Errorf("certificate validity begins in the future") - } - if now.Sub(cert.NotBefore) > (2 * time.Minute) { - return fmt.Errorf("certificate validity begins more than two minutes in the past") - } - if cert.NotAfter.Sub(cert.NotBefore) != time.Hour { - return fmt.Errorf("certificate validity is not one hour") - } - - return nil - }, + Config: selfSignedCertConfigWithDeprecatedKeyAlgorithm(1, 0), + Check: r.ComposeAggregateTestCheckFunc( + testCheckPEMFormat("tls_self_signed_cert.test1", "cert_pem", PreambleCertificate), + testCheckPEMCertificateSubject("tls_self_signed_cert.test1", "cert_pem", &pkix.Name{ + SerialNumber: "2", + CommonName: "example.com", + Organization: []string{"Example, Inc"}, + OrganizationalUnit: []string{"Department of Terraform Testing"}, + StreetAddress: []string{"5879 Cotton Link"}, + Locality: []string{"Pirate Harbor"}, + Province: []string{"CA"}, + Country: []string{"US"}, + PostalCode: []string{"95559-1227"}, + }), + testCheckPEMCertificateDNSNames("tls_self_signed_cert.test1", "cert_pem", []string{ + "example.com", + "example.net", + }), + testCheckPEMCertificateIPAddresses("tls_self_signed_cert.test1", "cert_pem", []net.IP{ + net.ParseIP("127.0.0.1"), + net.ParseIP("127.0.0.2"), + }), + testCheckPEMCertificateURIs("tls_self_signed_cert.test1", "cert_pem", []*url.URL{ + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "ca", + }, + { + Scheme: "spiffe", + Host: "example-trust-domain", + Path: "ca2", + }, + }), + testCheckPEMCertificateKeyUsage("tls_self_signed_cert.test1", "cert_pem", x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature), + testCheckPEMCertificateExtKeyUsages("tls_self_signed_cert.test1", "cert_pem", []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }), + testCheckPEMCertificateDuration("tls_self_signed_cert.test1", "cert_pem", time.Hour), + ), }, }, }) @@ -330,69 +150,46 @@ func TestAccSelfSignedCertRecreatesAfterExpired(t *testing.T) { PreCheck: setTimeForTest("2019-06-14T12:00:00Z"), Steps: []r.TestStep{ { - Config: selfSignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - previousCert = got + Config: selfSignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_self_signed_cert.test1", "cert_pem", func(value string) error { + previousCert = value return nil - }, + }), }, { - Config: selfSignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if got != previousCert { + Config: selfSignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_self_signed_cert.test1", "cert_pem", func(value string) error { + if previousCert != value { return fmt.Errorf("certificate updated even though no time has passed") } - previousCert = got + previousCert = value return nil - }, + }), }, { PreConfig: setTimeForTest("2019-06-14T19:00:00Z"), - Config: selfSignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if got != previousCert { + Config: selfSignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_self_signed_cert.test1", "cert_pem", func(value string) error { + if previousCert != value { return fmt.Errorf("certificate updated even though not enough time has passed") } - previousCert = got + previousCert = value return nil - }, + }), }, { PreConfig: setTimeForTest("2019-06-14T21:00:00Z"), - Config: selfSignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if got == previousCert { + Config: selfSignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_self_signed_cert.test1", "cert_pem", func(value string) error { + if previousCert == value { return fmt.Errorf("certificate not updated even though passed early renewal") } - previousCert = got + previousCert = value return nil - }, + }), }, }, }) @@ -407,69 +204,46 @@ func TestAccSelfSignedCertNotRecreatedForEarlyRenewalUpdateInFuture(t *testing.T PreCheck: setTimeForTest("2019-06-14T12:00:00Z"), Steps: []r.TestStep{ { - Config: selfSignedCertConfig(10, 2, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - previousCert = got + Config: selfSignedCertConfig(10, 2), + Check: r.TestCheckResourceAttrWith("tls_self_signed_cert.test1", "cert_pem", func(value string) error { + previousCert = value return nil - }, + }), }, { - Config: selfSignedCertConfig(10, 3, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if got != previousCert { + Config: selfSignedCertConfig(10, 3), + Check: r.TestCheckResourceAttrWith("tls_self_signed_cert.test1", "cert_pem", func(value string) error { + if previousCert != value { return fmt.Errorf("certificate updated even though still time until early renewal") } - previousCert = got + previousCert = value return nil - }, + }), }, { PreConfig: setTimeForTest("2019-06-14T16:00:00Z"), - Config: selfSignedCertConfig(10, 3, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if got != previousCert { + Config: selfSignedCertConfig(10, 3), + Check: r.TestCheckResourceAttrWith("tls_self_signed_cert.test1", "cert_pem", func(value string) error { + if previousCert != value { return fmt.Errorf("certificate updated even though still time until early renewal") } - previousCert = got + previousCert = value return nil - }, + }), }, { PreConfig: setTimeForTest("2019-06-14T16:00:00Z"), - Config: selfSignedCertConfig(10, 9, false), - Check: func(s *terraform.State) error { - gotUntyped := s.RootModule().Outputs["key_pem_1"].Value - got, ok := gotUntyped.(string) - if !ok { - return fmt.Errorf("output for \"key_pem_1\" is not a string") - } - - if got == previousCert { + Config: selfSignedCertConfig(10, 9), + Check: r.TestCheckResourceAttrWith("tls_self_signed_cert.test1", "cert_pem", func(value string) error { + if previousCert == value { return fmt.Errorf("certificate not updated even though early renewal time has passed") } - previousCert = got + previousCert = value return nil - }, + }), }, }, }) @@ -483,35 +257,26 @@ func TestAccSelfSignedCertSetSubjectKeyID(t *testing.T) { Steps: []r.TestStep{ { Config: fmt.Sprintf(` - resource "tls_self_signed_cert" "test" { - subject { - serial_number = "42" - } - validity_period_hours = 1 - allowed_uses = [] - set_subject_key_id = true - private_key_pem = < (2 * time.Minute) { + return fmt.Errorf("incorrect certificate validity period; begins more than 2 minutes in the past: %s", cert.NotBefore) + } + + if actual := cert.NotAfter.Sub(cert.NotBefore); actual != expected { + return fmt.Errorf("incorrect certificate validity duration: expected %s, got %s", expected, actual) + } + + return nil + }) +} + +func testCheckPEMCertificateAgainstPEMRootCA(name, key string, rootCA []byte) r.TestCheckFunc { + return testCheckPEMCertificateWith(name, key, func(crt *x509.Certificate) error { + // Certificate verification must fail if no CA Cert Pool is provided + _, err := crt.Verify(x509.VerifyOptions{}) + if err == nil { + return fmt.Errorf("incorrectly verified certificate") + } else if !errors.Is(err, x509.UnknownAuthorityError{Cert: crt}) { + return fmt.Errorf("incorrect verify error: expected UnknownAuthorityError, got %v", err) + } + + // Certificate verification must fail if an empty CA Cert Pool is provided + _, err = crt.Verify(x509.VerifyOptions{Roots: x509.NewCertPool()}) + if err == nil { + return fmt.Errorf("incorrectly verified certificate") + } else if !errors.Is(err, x509.UnknownAuthorityError{Cert: crt}) { + return fmt.Errorf("incorrect verify error: expected UnknownAuthorityError, got %v", err) + } + + // Certification verification must succeed now that we are providing the correct CA Cert Pool + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(rootCA) + if _, err = crt.Verify(x509.VerifyOptions{Roots: certPool}); err != nil { + return fmt.Errorf("verify failed: %s", err) + } + + return nil + }) +} + +func compareCertSubjects(expected, actualSubject *pkix.Name) error { + if expected.SerialNumber != "" && expected.SerialNumber != actualSubject.SerialNumber { + return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected.SerialNumber, actualSubject.SerialNumber) + } + if expected.CommonName != "" && expected.CommonName != actualSubject.CommonName { + return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected.CommonName, actualSubject.CommonName) + } + if len(expected.Organization) > 0 && !reflect.DeepEqual(expected.Organization, actualSubject.Organization) { + return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected.Organization, actualSubject.Organization) + } + if len(expected.OrganizationalUnit) > 0 && !reflect.DeepEqual(expected.OrganizationalUnit, actualSubject.OrganizationalUnit) { + return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected.OrganizationalUnit, actualSubject.OrganizationalUnit) + } + if len(expected.StreetAddress) > 0 && !reflect.DeepEqual(expected.StreetAddress, actualSubject.StreetAddress) { + return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected.StreetAddress, actualSubject.StreetAddress) + } + if len(expected.Locality) > 0 && !reflect.DeepEqual(expected.Locality, actualSubject.Locality) { + return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected.Locality, actualSubject.Locality) + } + if len(expected.Province) > 0 && !reflect.DeepEqual(expected.Province, actualSubject.Province) { + return fmt.Errorf("incorrect subject province: expected %v, got %v", expected.Province, actualSubject.Province) + } + if len(expected.Country) > 0 && !reflect.DeepEqual(expected.Country, actualSubject.Country) { + return fmt.Errorf("incorrect subject country: expected %v, got %v", expected.Country, actualSubject.Country) + } + if len(expected.PostalCode) > 0 && !reflect.DeepEqual(expected.PostalCode, actualSubject.PostalCode) { + return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected.PostalCode, actualSubject.PostalCode) + } + + return nil +} + +func compareCertDNSNames(expected, actual []string) error { + if len(expected) != len(actual) { + return fmt.Errorf("incorrect DNS names: expected %v, got %v", expected, actual) + } + + for i := range expected { + if !strings.EqualFold(expected[i], actual[i]) { + return fmt.Errorf("incorrect DNS names: expected %v, got %v", expected, actual) + } + } + + return nil +} + +func compareCertIPAddresses(expected, actual []net.IP) error { + if len(expected) != len(actual) { + return fmt.Errorf("incorrect IP addresses: expected %v, got %v", expected, actual) + } + + for i := range expected { + if !expected[i].Equal(actual[i]) { + return fmt.Errorf("incorrect IP addresses: expected %v, got %v", expected, actual) + } + } + + return nil +} + +func compareCertURIs(expected, actual []*url.URL) error { + if len(expected) != len(actual) { + return fmt.Errorf("incorrect URIs: expected %v, got %v", expected, actual) + } + + for i := range expected { + if !strings.EqualFold(expected[i].String(), actual[i].String()) { + return fmt.Errorf("incorrect URIs: expected %v, got %v", expected, actual) + } + } + + return nil +} + +func compareExtKeyUsages(expected, actual []x509.ExtKeyUsage) error { + if len(expected) != len(actual) { + return fmt.Errorf("incorrect Extended Key Usages: expected %v, got %v", expected, actual) + } + + for i := range expected { + if expected[i] != actual[i] { + return fmt.Errorf("incorrect Extended Key Usages: expected %v, got %v", expected, actual) + } + } + + return nil +} diff --git a/internal/provider/types.go b/internal/provider/types.go index 2bf69411..95ca765c 100644 --- a/internal/provider/types.go +++ b/internal/provider/types.go @@ -80,9 +80,10 @@ type PEMPreamble string const ( PreamblePublicKey PEMPreamble = "PUBLIC KEY" - PreamblePrivateKeyPKCS8 PEMPreamble = "PRIVATE KEY" - PreamblePrivateKeyRSA PEMPreamble = "RSA PRIVATE KEY" - PreamblePrivateKeyEC PEMPreamble = "EC PRIVATE KEY" + PreamblePrivateKeyPKCS8 PEMPreamble = "PRIVATE KEY" + PreamblePrivateKeyRSA PEMPreamble = "RSA PRIVATE KEY" + PreamblePrivateKeyEC PEMPreamble = "EC PRIVATE KEY" + PreamblePrivateKeyOpenSSH PEMPreamble = "OPENSSH PRIVATE KEY" PreambleCertificate PEMPreamble = "CERTIFICATE" PreambleCertificateRequest PEMPreamble = "CERTIFICATE REQUEST"