Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ services:
- docker
language: go
go:
- "1.11.x"
- "1.14.x"

env:
- GO111MODULE=on GOFLAGS=-mod=vendor
Expand Down
2 changes: 1 addition & 1 deletion GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ build: fmtcheck
go install

test: fmtcheck
go test -i $(TEST) || exit 1
go test $(TEST) || exit 1
echo $(TEST) | \
xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module github.com/terraform-providers/terraform-provider-tls

go 1.14

require (
github.com/hashicorp/terraform-plugin-sdk v1.0.0
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
Expand Down
1 change: 1 addition & 0 deletions tls/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func Provider() terraform.ResourceProvider {
"tls_locally_signed_cert": resourceLocallySignedCert(),
"tls_self_signed_cert": resourceSelfSignedCert(),
"tls_cert_request": resourceCertRequest(),
"tls_x509_crl": resourceCertRevocationList(),
},
DataSourcesMap: map[string]*schema.Resource{
"tls_public_key": dataSourcePublicKey(),
Expand Down
20 changes: 20 additions & 0 deletions tls/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ UI+cIbsQJcr9i0SADwhqs7XYIcEz9jDqrdezenaXdGtXYg==
-----END CERTIFICATE-----
`

var testCertificate = `
-----BEGIN CERTIFICATE-----
MIICsDCCAhkCAQIwDQYJKoZIhvcNAQELBQAwezELMAkGA1UEBhMCVVMxCzAJBgNV
BAgTAkNBMRYwFAYDVQQHEw1QaXJhdGUgSGFyYm9yMRUwEwYDVQQKEwxFeGFtcGxl
LCBJbmMxITAfBgNVBAsTGERlcGFydG1lbnQgb2YgQ0EgVGVzdGluZzENMAsGA1UE
AxMEcm9vdDAeFw0yMDAzMjkxNDM0MjFaFw0yMTAzMjkxNDM0MjFaMIHFMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVBpcmF0ZSBIYXJib3IxGTAX
BgNVBAkTEDU4NzkgQ290dG9uIExpbmsxEzARBgNVBBETCjk1NTU5LTEyMjcxFTAT
BgNVBAoTDEV4YW1wbGUsIEluYzEoMCYGA1UECxMfRGVwYXJ0bWVudCBvZiBUZXJy
YWZvcm0gVGVzdGluZzEUMBIGA1UEAxMLZXhhbXBsZS5jb20xCjAIBgNVBAUTATIw
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM8tqrjcP0Ln3qSj3JCKlZR/YVtG
EWzXwPfAEMGpcsig/tbeublLb3b8mI09r6ZfvohgUXjXrNMEYb0nnDh3hUWtE6fw
i4xaoeB1ewJR/azM3sSclxUGwHNPHFarPgONffXm9ub88U4uUrMLQ2CziHWIOIIP
0eTYxqNm4WcmKqPVAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAPIoSmOKjTu4frJpQ
wWUYrXgf63Ft7zbgPXkru8R4KswMQm3016sWibDfTkwLTp/Xli3AgVcE02cY+QQQ
/V+QZVWoZnQN7d6KPE7f1HiWAKOdP2/zWX3Ci/tvDkQUvpOUoZoRImoZKx+WwNC3
P+itMZWh3jPF4tj4WYHUnjyZqdk=
-----END CERTIFICATE-----
`

func setTimeForTest(timeStr string) func() {
return func() {
now = func() time.Time {
Expand Down
151 changes: 151 additions & 0 deletions tls/resource_cert_revocation_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package tls

import (
"crypto/rand"
"crypto/x509/pkix"
"encoding/pem"
"strings"
"time"

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

func resourceCertRevocationList() *schema.Resource {
s := map[string]*schema.Schema{
"certs_to_revoke": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Required: true,
Description: "PEM-encoded certificates to be revoked",
ForceNew: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While technically correct, it would be better, if we would update the list of certificates in the CRL, rather than create the new one every time, as then the expiry date will be updated too for example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... but I'm quite unsure on how to handle that lifecycle 🤔 , for instance the Id of the resource is calculated as a hash of the CRL PEM content (in the same way we do for locally signed certificates and other resources in this provider). When a CRL is "updated" actually what happens is that a new version of it is created, basically a new CRL itself. I was checking https://tools.ietf.org/html/rfc5280#section-5.1 trying to find a field that we can use to identify a CRL independently of the version, but I couldn't find any. But I can miss something, do you have any idea around this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'm not sure if I understand the problem. If you set the ID only in Create function and then don't touch it in Update, then what's the problem? I don't think there is a need to change the ID.

Aside from that, I think this is completely fine for the initial implementation. It can always be improved later 👍

StateFunc: func(v interface{}) string {
return hashForState(strings.Join(v.([]string), ","))
},
},
"early_renewal_hours": {
Type: schema.TypeInt,
Optional: true,
Default: 0,
ForceNew: true,
Description: "Number of hours before the CRL expiry when a new CRL will be generated",
},
"validity_period_hours": {
Type: schema.TypeInt,
Required: true,
Description: "Number of hours that the CRL will remain valid for",
ForceNew: true,
},
"validity_start_time": {
Type: schema.TypeString,
Computed: true,
},

"validity_end_time": {
Type: schema.TypeString,
Computed: true,
},
"ready_for_renewal": {
Type: schema.TypeBool,
Computed: true,
},
"crl_pem": {
Type: schema.TypeString,
Computed: true,
},
"ca_cert_pem": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "PEM-encoded CA certificate",
ForceNew: true,
StateFunc: func(v interface{}) string {
return hashForState(v.(string))
},
},
"ca_private_key_pem": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "PEM-encoded CA private key used to sign the CRL",
ForceNew: true,
Sensitive: true,
StateFunc: func(v interface{}) string {
return hashForState(v.(string))
},
},
"ca_key_algorithm": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "Name of the algorithm used to generate the CRL's private key",
ForceNew: true,
},
}

return &schema.Resource{
Create: CreateCRL,
Delete: DeleteCRL,
Read: ReadCRL,
//Update: UpdateCRL,
CustomizeDiff: CustomizeCertificateDiff,
Schema: s,
}
}

func CreateCRL(d *schema.ResourceData, meta interface{}) error {
certsToRevoke := make([]pkix.RevokedCertificate, 0)
for _, vi := range d.Get("certs_to_revoke").([]interface{}) {
certificate, err := decodeCertificateFromBytes([]byte(vi.(string)))
if err != nil {
return err
}
certsToRevoke = append(certsToRevoke, pkix.RevokedCertificate{
SerialNumber: certificate.SerialNumber,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RevocationTime field should be set here, otherwise revoked date in CRL is always: Revocation Date: Jan 1 00:00:00 1 GMT.

})
}
caKey, err := parsePrivateKey(d, "ca_private_key_pem", "ca_key_algorithm")
if err != nil {
return err
}
caCert, err := parseCertificate(d, "ca_cert_pem")
if err != nil {
return err
}

notBefore := now()
notAfter := notBefore.Add(time.Duration(d.Get("validity_period_hours").(int)) * time.Hour)
validFromBytes, err := notBefore.MarshalText()
if err != nil {
return err
}
validToBytes, err := notAfter.MarshalText()
if err != nil {
return err
}

crlBytes, err := caCert.CreateCRL(rand.Reader, caKey, certsToRevoke, notBefore, notAfter)
if err != nil {
return err
}

crlPem := string(pem.EncodeToMemory(&pem.Block{Type: "X509 CRL", Bytes: crlBytes}))

d.SetId(hashForState(string(crlBytes)))
d.Set("crl_pem", crlPem)
d.Set("ready_for_renewal", false)
d.Set("validity_start_time", string(validFromBytes))
d.Set("validity_end_time", string(validToBytes))
return nil
}

func DeleteCRL(d *schema.ResourceData, meta interface{}) error {
d.SetId("")
return nil
}

func ReadCRL(d *schema.ResourceData, meta interface{}) error {
return nil
}

//func UpdateCRL(d *schema.ResourceData, meta interface{}) error {
//return nil
//}
Loading