Skip to content

Commit

Permalink
support parsing certificate content hashicorp#4
Browse files Browse the repository at this point in the history
  • Loading branch information
iwarapter committed Apr 12, 2022
1 parent 156ae39 commit 44372d9
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 44 deletions.
115 changes: 71 additions & 44 deletions internal/provider/data_source_certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package provider
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"net/http"
"net/url"
Expand All @@ -24,18 +25,26 @@ func dataSourceCertificate() *schema.Resource {
Schema: map[string]*schema.Schema{
"url": {
Type: schema.TypeString,
Required: true,
Optional: true,
Description: "URL of the endpoint to get the certificates from. " +
fmt.Sprintf("Accepted schemes are: `%s`. ", strings.Join(SupportedURLSchemesStr(), "`, `")) +
"For scheme `https://` it will use the HTTP protocol and apply the `proxy` configuration " +
"of the provider, if set. For scheme `tls://` it will instead use a secure TCP socket.",
ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithScheme(SupportedURLSchemesStr())),
ConflictsWith: []string{"content"},
},
"content": {
Type: schema.TypeString,
Optional: true,
Description: "The content of the PEM encoded certificate",
ConflictsWith: []string{"url", "verify_chain"},
},
"verify_chain": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Whether to verify the certificate chain while parsing it or not (default: `true`).",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Whether to verify the certificate chain while parsing it or not (default: `true`).",
ConflictsWith: []string{"content"},
},
"certificates": {
Type: schema.TypeList,
Expand Down Expand Up @@ -115,51 +124,69 @@ func dataSourceCertificate() *schema.Resource {
func dataSourceCertificateRead(d *schema.ResourceData, m interface{}) error {
config := m.(*providerConfig)

targetURL, err := url.Parse(d.Get("url").(string))
if err != nil {
return err
}

// Determine if we should verify the chain of certificates, or skip said verification
shouldVerifyChain := d.Get("verify_chain").(bool)

// Ensure a port is set on the URL, or return an error
var peerCerts []*x509.Certificate
switch targetURL.Scheme {
case HTTPSScheme.String():
if targetURL.Port() == "" {
targetURL.Host += ":443"
if v, ok := d.GetOk("content"); ok {
block, _ := pem.Decode([]byte(v.(string)))
if block == nil {
return fmt.Errorf("failed to decode pem content")
}
if block.Type != "CERTIFICATE" {
return fmt.Errorf("pem must be of type 'CERTIFICATE'")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return fmt.Errorf("unable to parse the certificate %w", err)
}
err = d.Set("certificates", []interface{}{certificateToMap(cert)})
if err != nil {
return err
}
} else {
targetURL, err := url.Parse(d.Get("url").(string))
if err != nil {
return err
}

// TODO remove this branch and default to use `fetchPeerCertificatesViaHTTPS`
// as part of https://github.com/hashicorp/terraform-provider-tls/issues/183
if config.isProxyConfigured() {
peerCerts, err = fetchPeerCertificatesViaHTTPS(targetURL, shouldVerifyChain, config)
} else {
// Determine if we should verify the chain of certificates, or skip said verification
shouldVerifyChain := d.Get("verify_chain").(bool)

// Ensure a port is set on the URL, or return an error
var peerCerts []*x509.Certificate
switch targetURL.Scheme {
case HTTPSScheme.String():
if targetURL.Port() == "" {
targetURL.Host += ":443"
}

// TODO remove this branch and default to use `fetchPeerCertificatesViaHTTPS`
// as part of https://github.com/hashicorp/terraform-provider-tls/issues/183
if config.isProxyConfigured() {
peerCerts, err = fetchPeerCertificatesViaHTTPS(targetURL, shouldVerifyChain, config)
} else {
peerCerts, err = fetchPeerCertificatesViaTLS(targetURL, shouldVerifyChain)
}
case TLSScheme.String():
if targetURL.Port() == "" {
return fmt.Errorf("port missing from URL: %s", targetURL.String())
}

peerCerts, err = fetchPeerCertificatesViaTLS(targetURL, shouldVerifyChain)
default:
// NOTE: This should never happen, given we validate this at the schema level
return fmt.Errorf("unsupported scheme: %s", targetURL.Scheme)
}
case TLSScheme.String():
if targetURL.Port() == "" {
return fmt.Errorf("port missing from URL: %s", targetURL.String())
if err != nil {
return err
}

peerCerts, err = fetchPeerCertificatesViaTLS(targetURL, shouldVerifyChain)
default:
// NOTE: This should never happen, given we validate this at the schema level
return fmt.Errorf("unsupported scheme: %s", targetURL.Scheme)
}
if err != nil {
return err
}

// Convert peer certificates to a simple map
certs := make([]interface{}, len(peerCerts))
for i, peerCert := range peerCerts {
certs[len(peerCerts)-i-1] = certificateToMap(peerCert)
}
err = d.Set("certificates", certs)
if err != nil {
return err
// Convert peer certificates to a simple map
certs := make([]interface{}, len(peerCerts))
for i, peerCert := range peerCerts {
certs[len(peerCerts)-i-1] = certificateToMap(peerCert)
}
err = d.Set("certificates", certs)
if err != nil {
return err
}
}

d.SetId(time.Now().UTC().String())
Expand Down
47 changes: 47 additions & 0 deletions internal/provider/data_source_certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,53 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccDataSourceCertificate_CertificateContent(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
Providers: testProviders,

Steps: []resource.TestStep{
{

Config: `
data "tls_certificate" "test" {
content = file("testdata/tls_certs/certificate.pem")
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.#", "1"),

resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.signature_algorithm", "SHA256-RSA"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.public_key_algorithm", "RSA"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.serial_number", "266244246501122064554217434340898012243"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.is_ca", "false"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.version", "3"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.issuer", "CN=Root CA,O=Test Org,L=Here"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.subject", "CN=Child Cert,O=Child Co.,L=Everywhere"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.not_before", "2019-11-08T09:01:36Z"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.not_after", "2019-11-08T19:01:36Z"),
resource.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.sha1_fingerprint", "61b65624427d75b61169100836904e44364df817"),
),
},
{
Config: `
data "tls_certificate" "test" {
content = "not a pem"
}
`,
ExpectError: regexp.MustCompile("failed to decode pem content"),
},
{
Config: `
data "tls_certificate" "test" {
content = file("testdata/tls_certs/private.pem")
}
`,
ExpectError: regexp.MustCompile("pem must be of type 'CERTIFICATE'"),
},
},
})
}

func TestAccDataSourceCertificate_HTTPSScheme(t *testing.T) {
server, err := newHTTPServer()
if err != nil {
Expand Down
20 changes: 20 additions & 0 deletions internal/provider/testdata/tls_certs/certificate.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIRAMhMxtTgTXHTmo6ZU7OafFMwDQYJKoZIhvcNAQELBQAw
NDENMAsGA1UEBxMESGVyZTERMA8GA1UEChMIVGVzdCBPcmcxEDAOBgNVBAMTB1Jv
b3QgQ0EwHhcNMTkxMTA4MDkwMTM2WhcNMTkxMTA4MTkwMTM2WjA+MRMwEQYDVQQH
EwpFdmVyeXdoZXJlMRIwEAYDVQQKEwlDaGlsZCBDby4xEzARBgNVBAMTCkNoaWxk
IENlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQTeCu466xxnGr
CCrl823J4gGnp9AYb0laTP3uB4orXblTFq45ehDnEJXNykT+7acT8IrAjQlVQdl0
gLjNM6XjGkFQ7xRw5xi041vRrOtUzC1KxVqrcfT4WrKj6zM/MuK3hznc4NvvwdAx
Mb3Sk46yQ1PrMslsidDvhTAqXkVi3lD1bV/bpnDo3NRCldVpedE1wlR+6thXZN/Y
MggNuDdv6LDadVGlXgKw5KkEIgenGOzpX1o+GKGo5UWu1xoTHikVwEC1iVuCZax+
9FnHQO/q7SyF4Lb9d0j6vzrIAjzauGbiAsJya1GhYMF7INxzpSolzk0UYjT5Dxcq
d3VX1prxAgMBAAGjVjBUMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF
BQcDATAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFIBRoM9+w7/obXaqAmaCPyVf
ldxEMA0GCSqGSIb3DQEBCwUAA4IBAQCuXJkT+qD3STmyDlsJOQRLBKaECH+/0mw4
mn3oMikNfneybjhao+fpwTgFup3KIrdIgbuciHfSTZzWT6mDs9bUdZZLccU6cVRh
WiX0I1eppjQyOT7PuXDsOsBUMf+et5WuGYrtKsib07q2rHPtTq72iftANtWbznfq
DsM3TQL4LuEE9V2lU2L2f3kXKrkYzLJj7R4sGck5Fo/E8eeIFm1Z5FCPcia82N+C
xDsNFvV3r8TsRH60IxFekKddI+ivepa97SvC4r+69MPyxULHNwDtSL+8T4q01LEP
VKT7dWjBK3K0xxH0SPCtlqRbGalWz4adNNHazN/x7ebK+WB9ReSM
-----END CERTIFICATE-----

0 comments on commit 44372d9

Please sign in to comment.