Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vault_pki_secret_backend_cert: Report when renewal is pending #1597

Merged
merged 1 commit into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
66 changes: 53 additions & 13 deletions vault/resource_pki_secret_backend_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,13 @@ func pkiSecretBackendCertResource() *schema.Resource {
"expiration": {
Type: schema.TypeInt,
Computed: true,
Description: "The certificate expiration.",
Description: "The certificate expiration as a Unix-style timestamp.",
},
"renew_pending": {
Type: schema.TypeBool,
Computed: true,
Description: "Initially false, and then set to true during refresh once " +
"the expiration is less than min_seconds_remaining in the future.",
},
"revoke": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -245,24 +251,22 @@ func pkiSecretBackendCertCreate(d *schema.ResourceData, meta interface{}) error
d.Set("serial_number", resp.Data["serial_number"])
d.Set("expiration", resp.Data["expiration"])

if err := pkiSecretBackendCertSynchronizeRenewPending(d); err != nil {
return err
}

d.SetId(fmt.Sprintf("%s/%s/%s", backend, name, commonName))
return pkiSecretBackendCertRead(d, meta)
}

func checkPKICertExpiry(expiration int64) bool {
expiry := time.Unix(expiration, 0)
now := time.Now()

return now.After(expiry)
}

func pkiCertAutoRenewCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
// The Create and Read functions will both set renew_pending if
// the current time is after the min_seconds_remaining timestamp. During
// planning we respond to that by proposing automatic renewal, if enabled.
if d.Id() == "" || !d.Get("auto_renew").(bool) {
return nil
}

expiration := int64(d.Get("expiration").(int) - d.Get("min_seconds_remaining").(int))
if checkPKICertExpiry(expiration) {
if d.Get("renew_pending").(bool) {
log.Printf("[DEBUG] certificate %q is due for renewal", d.Id())
if err := d.SetNewComputed("certificate"); err != nil {
return err
Expand All @@ -272,6 +276,12 @@ func pkiCertAutoRenewCustomizeDiff(_ context.Context, d *schema.ResourceDiff, me
return err
}

// Renewing the certificate will reset the value of renew_pending
d.SetNewComputed("renew_pending")
if err := d.ForceNew("renew_pending"); err != nil {
return err
}

return nil
}

Expand All @@ -295,8 +305,12 @@ func pkiSecretBackendCertRead(d *schema.ResourceData, meta interface{}) error {
return nil
}

// trigger a resource re-creation whenever the engine's mount has disappeared
if !enabled {
if enabled {
if err := pkiSecretBackendCertSynchronizeRenewPending(d); err != nil {
return err
}
} else {
// trigger a resource re-creation whenever the engine's mount has disappeared
log.Printf("[WARN] Mount %q does not exist, setting resource for re-creation", path)
d.SetId("")
}
Expand Down Expand Up @@ -344,6 +358,32 @@ func pkiSecretBackendCertPath(backend string, name string) string {
return strings.Trim(backend, "/") + "/issue/" + strings.Trim(name, "/")
}

// pkiSecretBackendCertSynchronizeRenewPending calculates whether the
// expiration time of the certificate is fewer than min_seconds_remaining
// seconds in the future (relative to the current system time), and then
// updates the renew_pending attribute accordingly.
func pkiSecretBackendCertSynchronizeRenewPending(d *schema.ResourceData) error {
if _, ok := d.Get("renew_pending").(bool); !ok {
// pkiSecretBackendCertRead is shared between vault_pki_secret_backend_cert
// and vault_pki_secret_backend_root_cert, and the latter doesn't have
// an auto-renew mechanism so doesn't have a "renew_pending" attribute
// to update.
return nil
}

expiration := d.Get("expiration").(int)
earlyRenew := d.Get("min_seconds_remaining").(int)
effectiveExpiration := int64(expiration - earlyRenew)
return d.Set("renew_pending", checkPKICertExpiry(effectiveExpiration))
}

func checkPKICertExpiry(expiration int64) bool {
expiry := time.Unix(expiration, 0)
now := time.Now()

return now.After(expiry)
}

func convertIntoSliceOfString(slice interface{}) []string {
intSlice := slice.([]interface{})
strSlice := make([]string, len(intSlice))
Expand Down
8 changes: 8 additions & 0 deletions vault/resource_pki_secret_backend_cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func TestPkiSecretBackendCert_renew(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "revoke", "false"),
resource.TestCheckResourceAttrSet(resourceName, "expiration"),
resource.TestCheckResourceAttrSet(resourceName, "serial_number"),
resource.TestCheckResourceAttrSet(resourceName, "renew_pending"),
}

resource.Test(t, resource.TestCase{
Expand All @@ -200,6 +201,13 @@ func TestPkiSecretBackendCert_renew(t *testing.T) {
},
{
// test renewal based on cert expiry
// NOTE: Ideally we'd also directly test that the refreshed
// state has renew_pending set to true before creating the plan,
// but the test harness only exposes the state after applying
// the plan so we can't make assertions against the intermediate
// refresh and planning steps. Therefore we're only testing
// that renew_pending got set to true indirectly by observing
// that it then caused the certificate to get re-issued.
PreConfig: testWaitCertExpiry(store),
Config: testPkiSecretBackendCertConfig_renew(path),
Check: resource.ComposeTestCheckFunc(
Expand Down
12 changes: 11 additions & 1 deletion vault/resource_pki_secret_backend_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,13 @@ func pkiSecretBackendSignResource() *schema.Resource {
"expiration": {
Type: schema.TypeInt,
Computed: true,
Description: "The certificate expiration.",
Description: "The certificate expiration as a Unix-style timestamp.",
},
"renew_pending": {
Type: schema.TypeBool,
Computed: true,
Description: "Initially false, and then set to true during refresh once " +
"the expiration is less than min_seconds_remaining in the future.",
},
},
}
Expand Down Expand Up @@ -236,6 +242,10 @@ func pkiSecretBackendSignCreate(d *schema.ResourceData, meta interface{}) error
d.Set("serial_number", resp.Data["serial_number"])
d.Set("expiration", resp.Data["expiration"])

if err := pkiSecretBackendCertSynchronizeRenewPending(d); err != nil {
return err
}

d.SetId(fmt.Sprintf("%s/%s/%s", backend, name, commonName))

return pkiSecretBackendCertRead(d, meta)
Expand Down
1 change: 1 addition & 0 deletions vault/resource_pki_secret_backend_sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ func TestPkiSecretBackendSign_renew(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "min_seconds_remaining", "3595"),
resource.TestCheckResourceAttrSet(resourceName, "expiration"),
resource.TestCheckResourceAttrSet(resourceName, "serial"),
resource.TestCheckResourceAttrSet(resourceName, "renew_pending"),
testValidateCSR(resourceName),
}
resource.Test(t, resource.TestCase{
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/pki_secret_backend_cert.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,5 @@ In addition to the fields above, the following attributes are exported:
* `serial_number` - The serial number

* `expiration` - The expiration date of the certificate in unix epoch format

benashz marked this conversation as resolved.
Show resolved Hide resolved
* `renew_pending` - `true` if the current time (during refresh) is after the start of the early renewal window declared by `min_seconds_remaining`, and `false` otherwise; if `auto_renew` is set to `true` then the provider will plan to replace the certificate once renewal is pending.
benashz marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions website/docs/r/pki_secret_backend_sign.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ In addition to the fields above, the following attributes are exported:

* `expiration` - The expiration date of the certificate in unix epoch format

* `renew_pending` - `true` if the current time (during refresh) is after the start of the early renewal window declared by `min_seconds_remaining`, and `false` otherwise; if `auto_renew` is set to `true` then the provider will plan to replace the certificate once renewal is pending.

## Deprecations

* `serial` - Use `serial_number` instead.