Structure of ACME Tidy (#20494)
* Structure of ACME Tidy. * The tidy endpoints/call information. * Counts for status plumbing. * Update typo calls, add information saving date of account creation. * Missed some field locations. * Set-up of Tidy command. * Proper tidy function; lock to work with * Remove order safety buffer. * Missed a field. * Read lock for account creation; Write lock for tidy (account deletion) * Type issues fixed. * fix range operator. * Fix path_tidy read. * Add fields to auto-tidy config. * Add (and standardize) Tidy Config Response * Test pass, consistent fields * Changes from PR-Reviews. * Update test to updated default due to PR-Review.
This commit is contained in:
parent
b8ac5ec2da
commit
edbbd3106c
|
@ -217,6 +217,8 @@ type acmeAccount struct {
|
|||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed"`
|
||||
Jwk []byte `json:"jwk"`
|
||||
AcmeDirectory string `json:"acme-directory"`
|
||||
AccountCreatedDate time.Time `json:"account_created_date"`
|
||||
AccountRevokedDate time.Time `json:"account_revoked_date"`
|
||||
}
|
||||
|
||||
type acmeOrder struct {
|
||||
|
@ -288,6 +290,7 @@ func (a *acmeState) CreateAccount(ac *acmeContext, c *jwsCtx, contact []string,
|
|||
Jwk: c.Jwk,
|
||||
Status: StatusValid,
|
||||
AcmeDirectory: ac.acmeDirectory,
|
||||
AccountCreatedDate: time.Now(),
|
||||
}
|
||||
json, err := logical.StorageEntryJSON(acmeAccountPrefix+c.Kid, acct)
|
||||
if err != nil {
|
||||
|
|
|
@ -346,6 +346,8 @@ type backend struct {
|
|||
|
||||
// Context around ACME operations
|
||||
acmeState *acmeState
|
||||
acmeAccountLock sync.RWMutex // (Write) Locked on Tidy, (Read) Locked on Account Creation
|
||||
// TODO: Stress test this - eg. creating an order while an account is being revoked
|
||||
}
|
||||
|
||||
type roleOperation func(ctx context.Context, req *logical.Request, data *framework.FieldData, role *roleEntry) (*logical.Response, error)
|
||||
|
|
|
@ -4030,6 +4030,12 @@ func TestBackend_RevokePlusTidy_Intermediate(t *testing.T) {
|
|||
"revocation_queue_deleted_count": json.Number("0"),
|
||||
"cross_revoked_cert_deleted_count": json.Number("0"),
|
||||
"internal_backend_uuid": backendUUID,
|
||||
"tidy_acme": false,
|
||||
"acme_account_safety_buffer": json.Number("2592000"),
|
||||
"acme_orders_deleted_count": json.Number("0"),
|
||||
"acme_account_revoked_count": json.Number("0"),
|
||||
"acme_account_deleted_count": json.Number("0"),
|
||||
"total_acme_account_count": json.Number("0"),
|
||||
}
|
||||
// Let's copy the times from the response so that we can use deep.Equal()
|
||||
timeStarted, ok := tidyStatus.Data["time_started"]
|
||||
|
|
|
@ -491,6 +491,23 @@ this removes ALL issuers within the mount (and is thus not desirable
|
|||
in most operational scenarios).`,
|
||||
}
|
||||
|
||||
fields["tidy_acme"] = &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Description: `Set to true to enable tidying ACME accounts,
|
||||
orders and authorizations. ACME orders are tidied (deleted)
|
||||
safety_buffer after the certificate associated with them expires,
|
||||
or after the order and relevant authorizations have expired if no
|
||||
certificate was produced. Authorizations are tidied with the
|
||||
corresponding order.
|
||||
|
||||
When a valid ACME Account is at least acme_account_safety_buffer
|
||||
old, and has no remaining orders associated with it, the account is
|
||||
marked as revoked. After another acme_account_safety_buffer has
|
||||
passed from the revocation or deactivation date, a revoked or
|
||||
deactivated ACME account is deleted.`,
|
||||
Default: false,
|
||||
}
|
||||
|
||||
fields["safety_buffer"] = &framework.FieldSchema{
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: `The amount of extra time that must have passed
|
||||
|
@ -509,6 +526,14 @@ Defaults to 8760 hours (1 year).`,
|
|||
Default: int(defaultTidyConfig.IssuerSafetyBuffer / time.Second), // TypeDurationSecond currently requires defaults to be int
|
||||
}
|
||||
|
||||
fields["acme_account_safety_buffer"] = &framework.FieldSchema{
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: `The amount of time that must pass after creation
|
||||
that an account with no orders is marked revoked, and the amount of time
|
||||
after being marked revoked or dea`,
|
||||
Default: int(defaultTidyConfig.AcmeAccountSafetyBuffer / time.Second), // TypeDurationSecond currently requires defaults to be int
|
||||
}
|
||||
|
||||
fields["pause_duration"] = &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `The amount of time to wait between processing
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
|
||||
|
@ -236,6 +237,8 @@ func (b *backend) acmeNewAccountCreateHandler(acmeCtx *acmeContext, userCtx *jws
|
|||
// return nil, fmt.Errorf("terms of service not agreed to: %w", ErrUserActionRequired)
|
||||
//}
|
||||
|
||||
b.acmeAccountLock.RLock() // Prevents Account Creation and Tidy Interfering
|
||||
defer b.acmeAccountLock.RUnlock()
|
||||
accountByKid, err := b.acmeState.CreateAccount(acmeCtx, userCtx, contact, termsOfServiceAgreed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create account: %w", err)
|
||||
|
@ -282,6 +285,7 @@ func (b *backend) acmeNewAccountUpdateHandler(acmeCtx *acmeContext, userCtx *jws
|
|||
// TODO: This should cancel any ongoing operations (do not revoke certs),
|
||||
// perhaps we should delete this account here?
|
||||
account.Status = StatusDeactivated
|
||||
account.AccountRevokedDate = time.Now()
|
||||
}
|
||||
|
||||
if shouldUpdate {
|
||||
|
@ -294,3 +298,90 @@ func (b *backend) acmeNewAccountUpdateHandler(acmeCtx *acmeContext, userCtx *jws
|
|||
resp := formatAccountResponse(acmeCtx, account)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *backend) tidyAcmeAccountByThumbprint(as *acmeState, ac *acmeContext, keyThumbprint string, certTidyBuffer, accountTidyBuffer time.Duration) error {
|
||||
thumbprintEntry, err := ac.sc.Storage.Get(ac.sc.Context, acmeThumbprintPrefix+keyThumbprint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving thumbprint entry %v, unable to find corresponding account entry: %w", keyThumbprint, err)
|
||||
}
|
||||
if thumbprintEntry == nil {
|
||||
return fmt.Errorf("empty thumbprint entry %v, unable to find corresponding account entry", keyThumbprint)
|
||||
}
|
||||
|
||||
var thumbprint acmeThumbprint
|
||||
err = thumbprintEntry.DecodeJSON(&thumbprint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode thumbprint entry %v to find account entry: %w", keyThumbprint, err)
|
||||
}
|
||||
|
||||
if len(thumbprint.Kid) == 0 {
|
||||
return fmt.Errorf("unable to find account entry: empty kid within thumbprint entry: %s", keyThumbprint)
|
||||
}
|
||||
|
||||
// Now Get the Account:
|
||||
accountEntry, err := ac.sc.Storage.Get(ac.sc.Context, acmeAccountPrefix+thumbprint.Kid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if accountEntry == nil {
|
||||
// We delete the Thumbprint Associated with the Account, and we are done
|
||||
err = ac.sc.Storage.Delete(ac.sc.Context, acmeThumbprintPrefix+keyThumbprint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tidyStatusIncDeletedAcmeAccountCount()
|
||||
return nil
|
||||
}
|
||||
|
||||
var account acmeAccount
|
||||
err = accountEntry.DecodeJSON(&account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Tidy Orders On the Account
|
||||
orderIds, err := as.ListOrderIds(ac, thumbprint.Kid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allOrdersTidied := true
|
||||
for _, orderId := range orderIds {
|
||||
wasTidied, err := b.acmeTidyOrder(ac, thumbprint.Kid, acmeAccountPrefix+thumbprint.Kid+"/orders/"+orderId, ac.sc, certTidyBuffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !wasTidied {
|
||||
allOrdersTidied = false
|
||||
}
|
||||
}
|
||||
|
||||
if allOrdersTidied && time.Now().After(account.AccountCreatedDate.Add(accountTidyBuffer)) {
|
||||
// Tidy this account
|
||||
// If it is Revoked or Deactivated:
|
||||
if (account.Status == StatusRevoked || account.Status == StatusDeactivated) && time.Now().After(account.AccountRevokedDate.Add(accountTidyBuffer)) {
|
||||
// We Delete the Account Associated with this Thumbprint:
|
||||
err = ac.sc.Storage.Delete(ac.sc.Context, acmeAccountPrefix+thumbprint.Kid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now we delete the Thumbprint Associated with the Account:
|
||||
err = ac.sc.Storage.Delete(ac.sc.Context, acmeThumbprintPrefix+keyThumbprint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tidyStatusIncDeletedAcmeAccountCount()
|
||||
} else if account.Status == StatusValid {
|
||||
// Revoke This Account
|
||||
account.AccountRevokedDate = time.Now()
|
||||
account.Status = StatusRevoked
|
||||
err := as.UpdateAccount(ac, &account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.tidyStatusIncRevAcmeAccountCount()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -884,3 +884,64 @@ func parseOrderIdentifiers(data map[string]interface{}) ([]*ACMEIdentifier, erro
|
|||
|
||||
return identifiers, nil
|
||||
}
|
||||
|
||||
func (b *backend) acmeTidyOrder(ac *acmeContext, accountId string, orderPath string, sc *storageContext, certTidyBuffer time.Duration) (wasTidied bool, err error) {
|
||||
// First we get the order; note that the orderPath includes the account
|
||||
// It's only accessed at acme/orders/<order_id> with the account context
|
||||
// It's saved at acme/<account_id>/orders/<orderId>
|
||||
entry, err := ac.sc.Storage.Get(ac.sc.Context, orderPath)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error loading order: %w", err)
|
||||
}
|
||||
if entry == nil {
|
||||
return false, fmt.Errorf("order does not exist: %w", ErrMalformed)
|
||||
}
|
||||
var order acmeOrder
|
||||
err = entry.DecodeJSON(&order)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error decoding order: %w", err)
|
||||
}
|
||||
|
||||
// Determine whether we should tidy this order
|
||||
shouldTidy := false
|
||||
// It is faster to check certificate information on the order entry rather than fetch the cert entry to parse:
|
||||
if !order.CertificateExpiry.IsZero() {
|
||||
// This implies that a certificate exists
|
||||
// When a certificate exists, we want to expire and tidy the order when we tidy the certificate:
|
||||
if time.Now().After(order.CertificateExpiry.Add(certTidyBuffer)) { // It's time to clean
|
||||
shouldTidy = true
|
||||
}
|
||||
} else {
|
||||
// This implies that no certificate exists
|
||||
// In this case, we want to expire the order after it has expired (+ some safety buffer)
|
||||
if time.Now().After(order.Expires) {
|
||||
shouldTidy = true
|
||||
}
|
||||
}
|
||||
if shouldTidy == false {
|
||||
return shouldTidy, nil
|
||||
}
|
||||
|
||||
// Tidy this Order
|
||||
// That includes any certificate acme/<account_id>/orders/orderPath/cert
|
||||
// That also includes any related authorizations: acme/<account_id>/authorizations/<auth_id>
|
||||
|
||||
// First Authorizations
|
||||
for _, authorizationId := range order.AuthorizationIds {
|
||||
err = ac.sc.Storage.Delete(ac.sc.Context, getAuthorizationPath(accountId, authorizationId))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Tidy will Take Care of the Certificate
|
||||
|
||||
// And Finally, the order:
|
||||
err = ac.sc.Storage.Delete(ac.sc.Context, orderPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
b.tidyStatusIncDelAcmeOrderCount()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ type tidyStatus struct {
|
|||
safetyBuffer int
|
||||
issuerSafetyBuffer int
|
||||
revQueueSafetyBuffer int
|
||||
acmeAccountSafetyBuffer int
|
||||
|
||||
tidyCertStore bool
|
||||
tidyRevokedCerts bool
|
||||
|
@ -46,6 +47,7 @@ type tidyStatus struct {
|
|||
tidyBackupBundle bool
|
||||
tidyRevocationQueue bool
|
||||
tidyCrossRevokedCerts bool
|
||||
tidyAcme bool
|
||||
pauseDuration string
|
||||
|
||||
// Status
|
||||
|
@ -62,6 +64,11 @@ type tidyStatus struct {
|
|||
missingIssuerCertCount uint
|
||||
revQueueDeletedCount uint
|
||||
crossRevokedDeletedCount uint
|
||||
|
||||
acmeAccountsCount uint
|
||||
acmeAccountsRevokedCount uint
|
||||
acmeAccountsDeletedCount uint
|
||||
acmeOrdersDeletedCount uint
|
||||
}
|
||||
|
||||
type tidyConfig struct {
|
||||
|
@ -72,8 +79,10 @@ type tidyConfig struct {
|
|||
IssuerAssocs bool `json:"tidy_revoked_cert_issuer_associations"`
|
||||
ExpiredIssuers bool `json:"tidy_expired_issuers"`
|
||||
BackupBundle bool `json:"tidy_move_legacy_ca_bundle"`
|
||||
TidyAcme bool `json:"tidy_acme"`
|
||||
SafetyBuffer time.Duration `json:"safety_buffer"`
|
||||
IssuerSafetyBuffer time.Duration `json:"issuer_safety_buffer"`
|
||||
AcmeAccountSafetyBuffer time.Duration `json:"acme_account_safety_buffer"`
|
||||
PauseDuration time.Duration `json:"pause_duration"`
|
||||
MaintainCount bool `json:"maintain_stored_certificate_counts"`
|
||||
PublishMetrics bool `json:"publish_stored_certificate_count_metrics"`
|
||||
|
@ -90,8 +99,10 @@ var defaultTidyConfig = tidyConfig{
|
|||
IssuerAssocs: false,
|
||||
ExpiredIssuers: false,
|
||||
BackupBundle: false,
|
||||
TidyAcme: false,
|
||||
SafetyBuffer: 72 * time.Hour,
|
||||
IssuerSafetyBuffer: 365 * 24 * time.Hour,
|
||||
AcmeAccountSafetyBuffer: 30 * 24 * time.Hour,
|
||||
PauseDuration: 0 * time.Second,
|
||||
MaintainCount: false,
|
||||
PublishMetrics: false,
|
||||
|
@ -174,6 +185,16 @@ func pathTidyCancel(b *backend) *framework.Path {
|
|||
Description: `Tidy revoked certificate issuer associations`,
|
||||
Required: false,
|
||||
},
|
||||
"tidy_acme": {
|
||||
Type: framework.TypeBool,
|
||||
Description: `Tidy Unused Acme Accounts, and Orders`,
|
||||
Required: false,
|
||||
},
|
||||
"acme_account_safety_buffer": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `Safety buffer after creation after which accounts lacking orders are revoked`,
|
||||
Required: false,
|
||||
},
|
||||
"tidy_expired_issuers": {
|
||||
Type: framework.TypeBool,
|
||||
Description: `Tidy expired issuers`,
|
||||
|
@ -262,6 +283,26 @@ func pathTidyCancel(b *backend) *framework.Path {
|
|||
Type: framework.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
"total_acme_account_count": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `Total number of acme accounts iterated over`,
|
||||
Required: false,
|
||||
},
|
||||
"acme_account_deleted_count": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `The number of revoked acme accounts removed`,
|
||||
Required: false,
|
||||
},
|
||||
"acme_account_revoked_count": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `The number of unused acme accounts revoked`,
|
||||
Required: false,
|
||||
},
|
||||
"acme_orders_deleted_count": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `The number of expired, unused acme orders removed`,
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
|
@ -305,6 +346,11 @@ func pathTidyStatus(b *backend) *framework.Path {
|
|||
Description: `Revocation queue safety buffer`,
|
||||
Required: true,
|
||||
},
|
||||
"acme_account_safety_buffer": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `Safety buffer after creation after which accounts lacking orders are revoked`,
|
||||
Required: false,
|
||||
},
|
||||
"tidy_cert_store": {
|
||||
Type: framework.TypeBool,
|
||||
Description: `Tidy certificate store`,
|
||||
|
@ -330,6 +376,11 @@ func pathTidyStatus(b *backend) *framework.Path {
|
|||
Description: ``,
|
||||
Required: false,
|
||||
},
|
||||
"tidy_acme": {
|
||||
Type: framework.TypeBool,
|
||||
Description: `Tidy Unused Acme Accounts, and Orders`,
|
||||
Required: true,
|
||||
},
|
||||
"pause_duration": {
|
||||
Type: framework.TypeString,
|
||||
Description: `Duration to pause between tidying certificates`,
|
||||
|
@ -410,6 +461,26 @@ func pathTidyStatus(b *backend) *framework.Path {
|
|||
Type: framework.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"total_acme_account_count": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `Total number of acme accounts iterated over`,
|
||||
Required: false,
|
||||
},
|
||||
"acme_account_deleted_count": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `The number of revoked acme accounts removed`,
|
||||
Required: false,
|
||||
},
|
||||
"acme_account_revoked_count": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `The number of unused acme accounts revoked`,
|
||||
Required: false,
|
||||
},
|
||||
"acme_orders_deleted_count": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `The number of expired, unused acme orders removed`,
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
|
@ -478,6 +549,11 @@ func pathConfigAutoTidy(b *backend) *framework.Path {
|
|||
Description: `Specifies whether tidy expired issuers`,
|
||||
Required: true,
|
||||
},
|
||||
"tidy_acme": {
|
||||
Type: framework.TypeBool,
|
||||
Description: `Tidy Unused Acme Accounts, and Orders`,
|
||||
Required: true,
|
||||
},
|
||||
"safety_buffer": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `Safety buffer time duration`,
|
||||
|
@ -488,6 +564,11 @@ func pathConfigAutoTidy(b *backend) *framework.Path {
|
|||
Description: `Issuer safety buffer`,
|
||||
Required: true,
|
||||
},
|
||||
"acme_account_safety_buffer": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `Safety buffer after creation after which accounts lacking orders are revoked`,
|
||||
Required: false,
|
||||
},
|
||||
"pause_duration": {
|
||||
Type: framework.TypeString,
|
||||
Description: `Duration to pause between tidying certificates`,
|
||||
|
@ -561,6 +642,11 @@ func pathConfigAutoTidy(b *backend) *framework.Path {
|
|||
Description: `Specifies whether tidy expired issuers`,
|
||||
Required: true,
|
||||
},
|
||||
"tidy_acme": {
|
||||
Type: framework.TypeBool,
|
||||
Description: `Tidy Unused Acme Accounts, and Orders`,
|
||||
Required: true,
|
||||
},
|
||||
"safety_buffer": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `Safety buffer time duration`,
|
||||
|
@ -571,6 +657,11 @@ func pathConfigAutoTidy(b *backend) *framework.Path {
|
|||
Description: `Issuer safety buffer`,
|
||||
Required: true,
|
||||
},
|
||||
"acme_account_safety_buffer": {
|
||||
Type: framework.TypeInt,
|
||||
Description: `Safety buffer after creation after which accounts lacking orders are revoked`,
|
||||
Required: true,
|
||||
},
|
||||
"pause_duration": {
|
||||
Type: framework.TypeString,
|
||||
Description: `Duration to pause between tidying certificates`,
|
||||
|
@ -592,6 +683,14 @@ func pathConfigAutoTidy(b *backend) *framework.Path {
|
|||
Type: framework.TypeDurationSecond,
|
||||
Required: true,
|
||||
},
|
||||
"publish_stored_certificate_count_metrics": {
|
||||
Type: framework.TypeBool,
|
||||
Required: true,
|
||||
},
|
||||
"maintain_stored_certificate_counts": {
|
||||
Type: framework.TypeBool,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
|
@ -618,6 +717,8 @@ func (b *backend) pathTidyWrite(ctx context.Context, req *logical.Request, d *fr
|
|||
tidyRevocationQueue := d.Get("tidy_revocation_queue").(bool)
|
||||
queueSafetyBuffer := d.Get("revocation_queue_safety_buffer").(int)
|
||||
tidyCrossRevokedCerts := d.Get("tidy_cross_cluster_revoked_certs").(bool)
|
||||
tidyAcme := d.Get("tidy_acme").(bool)
|
||||
acmeAccountSafetyBuffer := d.Get("acme_account_safety_buffer").(int)
|
||||
|
||||
if safetyBuffer < 1 {
|
||||
return logical.ErrorResponse("safety_buffer must be greater than zero"), nil
|
||||
|
@ -631,6 +732,10 @@ func (b *backend) pathTidyWrite(ctx context.Context, req *logical.Request, d *fr
|
|||
return logical.ErrorResponse("revocation_queue_safety_buffer must be greater than zero"), nil
|
||||
}
|
||||
|
||||
if acmeAccountSafetyBuffer < 1 {
|
||||
return logical.ErrorResponse("acme_account_safety_buffer must be greater than zero"), nil
|
||||
}
|
||||
|
||||
if pauseDurationStr != "" {
|
||||
var err error
|
||||
pauseDuration, err = time.ParseDuration(pauseDurationStr)
|
||||
|
@ -646,6 +751,7 @@ func (b *backend) pathTidyWrite(ctx context.Context, req *logical.Request, d *fr
|
|||
bufferDuration := time.Duration(safetyBuffer) * time.Second
|
||||
issuerBufferDuration := time.Duration(issuerSafetyBuffer) * time.Second
|
||||
queueSafetyBufferDuration := time.Duration(queueSafetyBuffer) * time.Second
|
||||
acmeAccountSafetyBufferDuration := time.Duration(acmeAccountSafetyBuffer) * time.Second
|
||||
|
||||
// Manual run with constructed configuration.
|
||||
config := &tidyConfig{
|
||||
|
@ -662,6 +768,8 @@ func (b *backend) pathTidyWrite(ctx context.Context, req *logical.Request, d *fr
|
|||
RevocationQueue: tidyRevocationQueue,
|
||||
QueueSafetyBuffer: queueSafetyBufferDuration,
|
||||
CrossRevokedCerts: tidyCrossRevokedCerts,
|
||||
TidyAcme: tidyAcme,
|
||||
AcmeAccountSafetyBuffer: acmeAccountSafetyBufferDuration,
|
||||
}
|
||||
|
||||
if !atomic.CompareAndSwapUint32(b.tidyCASGuard, 0, 1) {
|
||||
|
@ -777,6 +885,17 @@ func (b *backend) startTidyOperation(req *logical.Request, config *tidyConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for cancel before continuing.
|
||||
if atomic.CompareAndSwapUint32(b.tidyCancelCAS, 1, 0) {
|
||||
return tidyCancelledError
|
||||
}
|
||||
|
||||
if config.TidyAcme {
|
||||
if err := b.doTidyAcme(ctx, req, logger, config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1381,6 +1500,53 @@ func (b *backend) doTidyCrossRevocationStore(ctx context.Context, req *logical.R
|
|||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) doTidyAcme(ctx context.Context, req *logical.Request, logger hclog.Logger, config *tidyConfig) error {
|
||||
b.acmeAccountLock.Lock()
|
||||
defer b.acmeAccountLock.Unlock()
|
||||
|
||||
sc := b.makeStorageContext(ctx, req.Storage)
|
||||
list, err := sc.Storage.List(ctx, acmeThumbprintPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.tidyStatusLock.Lock()
|
||||
b.tidyStatus.acmeAccountsCount = uint(len(list))
|
||||
b.tidyStatusLock.Unlock()
|
||||
|
||||
baseUrl, _, err := getAcmeBaseUrl(sc, req.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
acmeCtx := &acmeContext{
|
||||
baseUrl: baseUrl,
|
||||
sc: sc,
|
||||
}
|
||||
|
||||
for _, thumbprint := range list {
|
||||
err := b.tidyAcmeAccountByThumbprint(b.acmeState, acmeCtx, thumbprint, config.SafetyBuffer, config.AcmeAccountSafetyBuffer)
|
||||
if err != nil {
|
||||
logger.Warn("error tidying account %v: %v", thumbprint, err.Error())
|
||||
}
|
||||
|
||||
// Check for cancel before continuing.
|
||||
if atomic.CompareAndSwapUint32(b.tidyCancelCAS, 1, 0) {
|
||||
return tidyCancelledError
|
||||
}
|
||||
|
||||
// Check for pause duration to reduce resource consumption.
|
||||
if config.PauseDuration > (0 * time.Second) {
|
||||
b.acmeAccountLock.Unlock() // Correct the Lock
|
||||
time.Sleep(config.PauseDuration)
|
||||
b.acmeAccountLock.Lock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) pathTidyCancelWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
if atomic.LoadUint32(b.tidyCASGuard) == 0 {
|
||||
resp := &logical.Response{}
|
||||
|
@ -1419,6 +1585,7 @@ func (b *backend) pathTidyStatusRead(_ context.Context, _ *logical.Request, _ *f
|
|||
"tidy_move_legacy_ca_bundle": nil,
|
||||
"tidy_revocation_queue": nil,
|
||||
"tidy_cross_cluster_revoked_certs": nil,
|
||||
"tidy_acme": nil,
|
||||
"pause_duration": nil,
|
||||
"state": "Inactive",
|
||||
"error": nil,
|
||||
|
@ -1433,6 +1600,11 @@ func (b *backend) pathTidyStatusRead(_ context.Context, _ *logical.Request, _ *f
|
|||
"internal_backend_uuid": nil,
|
||||
"revocation_queue_deleted_count": nil,
|
||||
"cross_revoked_cert_deleted_count": nil,
|
||||
"total_acme_account_count": nil,
|
||||
"acme_account_deleted_count": nil,
|
||||
"acme_account_revoked_count": nil,
|
||||
"acme_orders_deleted_count": nil,
|
||||
"acme_account_safety_buffer": nil,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1463,6 +1635,7 @@ func (b *backend) pathTidyStatusRead(_ context.Context, _ *logical.Request, _ *f
|
|||
resp.Data["tidy_move_legacy_ca_bundle"] = b.tidyStatus.tidyBackupBundle
|
||||
resp.Data["tidy_revocation_queue"] = b.tidyStatus.tidyRevocationQueue
|
||||
resp.Data["tidy_cross_cluster_revoked_certs"] = b.tidyStatus.tidyCrossRevokedCerts
|
||||
resp.Data["tidy_acme"] = b.tidyStatus.tidyAcme
|
||||
resp.Data["pause_duration"] = b.tidyStatus.pauseDuration
|
||||
resp.Data["time_started"] = b.tidyStatus.timeStarted
|
||||
resp.Data["message"] = b.tidyStatus.message
|
||||
|
@ -1473,6 +1646,11 @@ func (b *backend) pathTidyStatusRead(_ context.Context, _ *logical.Request, _ *f
|
|||
resp.Data["cross_revoked_cert_deleted_count"] = b.tidyStatus.crossRevokedDeletedCount
|
||||
resp.Data["revocation_queue_safety_buffer"] = b.tidyStatus.revQueueSafetyBuffer
|
||||
resp.Data["last_auto_tidy_finished"] = b.lastTidy
|
||||
resp.Data["total_acme_account_count"] = b.tidyStatus.acmeAccountsCount
|
||||
resp.Data["acme_account_deleted_count"] = b.tidyStatus.acmeAccountsDeletedCount
|
||||
resp.Data["acme_account_revoked_count"] = b.tidyStatus.acmeAccountsRevokedCount
|
||||
resp.Data["acme_orders_deleted_count"] = b.tidyStatus.acmeOrdersDeletedCount
|
||||
resp.Data["acme_account_safety_buffer"] = b.tidyStatus.acmeAccountSafetyBuffer
|
||||
|
||||
switch b.tidyStatus.state {
|
||||
case tidyStatusStarted:
|
||||
|
@ -1505,23 +1683,7 @@ func (b *backend) pathConfigAutoTidyRead(ctx context.Context, req *logical.Reque
|
|||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"enabled": config.Enabled,
|
||||
"interval_duration": int(config.Interval / time.Second),
|
||||
"tidy_cert_store": config.CertStore,
|
||||
"tidy_revoked_certs": config.RevokedCerts,
|
||||
"tidy_revoked_cert_issuer_associations": config.IssuerAssocs,
|
||||
"tidy_expired_issuers": config.ExpiredIssuers,
|
||||
"safety_buffer": int(config.SafetyBuffer / time.Second),
|
||||
"issuer_safety_buffer": int(config.IssuerSafetyBuffer / time.Second),
|
||||
"pause_duration": config.PauseDuration.String(),
|
||||
"publish_stored_certificate_count_metrics": config.PublishMetrics,
|
||||
"maintain_stored_certificate_counts": config.MaintainCount,
|
||||
"tidy_move_legacy_ca_bundle": config.BackupBundle,
|
||||
"tidy_revocation_queue": config.RevocationQueue,
|
||||
"revocation_queue_safety_buffer": int(config.QueueSafetyBuffer / time.Second),
|
||||
"tidy_cross_cluster_revoked_certs": config.CrossRevokedCerts,
|
||||
},
|
||||
Data: getTidyConfigData(*config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -1623,21 +1785,7 @@ func (b *backend) pathConfigAutoTidyWrite(ctx context.Context, req *logical.Requ
|
|||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"enabled": config.Enabled,
|
||||
"interval_duration": int(config.Interval / time.Second),
|
||||
"tidy_cert_store": config.CertStore,
|
||||
"tidy_revoked_certs": config.RevokedCerts,
|
||||
"tidy_revoked_cert_issuer_associations": config.IssuerAssocs,
|
||||
"tidy_expired_issuers": config.ExpiredIssuers,
|
||||
"tidy_move_legacy_ca_bundle": config.BackupBundle,
|
||||
"safety_buffer": int(config.SafetyBuffer / time.Second),
|
||||
"issuer_safety_buffer": int(config.IssuerSafetyBuffer / time.Second),
|
||||
"pause_duration": config.PauseDuration.String(),
|
||||
"tidy_revocation_queue": config.RevocationQueue,
|
||||
"revocation_queue_safety_buffer": int(config.QueueSafetyBuffer / time.Second),
|
||||
"tidy_cross_cluster_revoked_certs": config.CrossRevokedCerts,
|
||||
},
|
||||
Data: getTidyConfigData(*config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -1649,6 +1797,7 @@ func (b *backend) tidyStatusStart(config *tidyConfig) {
|
|||
safetyBuffer: int(config.SafetyBuffer / time.Second),
|
||||
issuerSafetyBuffer: int(config.IssuerSafetyBuffer / time.Second),
|
||||
revQueueSafetyBuffer: int(config.QueueSafetyBuffer / time.Second),
|
||||
acmeAccountSafetyBuffer: int(config.AcmeAccountSafetyBuffer / time.Second),
|
||||
tidyCertStore: config.CertStore,
|
||||
tidyRevokedCerts: config.RevokedCerts,
|
||||
tidyRevokedAssocs: config.IssuerAssocs,
|
||||
|
@ -1656,6 +1805,7 @@ func (b *backend) tidyStatusStart(config *tidyConfig) {
|
|||
tidyBackupBundle: config.BackupBundle,
|
||||
tidyRevocationQueue: config.RevocationQueue,
|
||||
tidyCrossRevokedCerts: config.CrossRevokedCerts,
|
||||
tidyAcme: config.TidyAcme,
|
||||
pauseDuration: config.PauseDuration.String(),
|
||||
|
||||
state: tidyStatusStarted,
|
||||
|
@ -1737,6 +1887,27 @@ func (b *backend) tidyStatusIncCrossRevCertCount() {
|
|||
b.tidyStatus.crossRevokedDeletedCount++
|
||||
}
|
||||
|
||||
func (b *backend) tidyStatusIncRevAcmeAccountCount() {
|
||||
b.tidyStatusLock.Lock()
|
||||
defer b.tidyStatusLock.Unlock()
|
||||
|
||||
b.tidyStatus.acmeAccountsRevokedCount++
|
||||
}
|
||||
|
||||
func (b *backend) tidyStatusIncDeletedAcmeAccountCount() {
|
||||
b.tidyStatusLock.Lock()
|
||||
defer b.tidyStatusLock.Unlock()
|
||||
|
||||
b.tidyStatus.acmeAccountsDeletedCount++
|
||||
}
|
||||
|
||||
func (b *backend) tidyStatusIncDelAcmeOrderCount() {
|
||||
b.tidyStatusLock.Lock()
|
||||
defer b.tidyStatusLock.Unlock()
|
||||
|
||||
b.tidyStatus.acmeOrdersDeletedCount++
|
||||
}
|
||||
|
||||
const pathTidyHelpSyn = `
|
||||
Tidy up the backend by removing expired certificates, revocation information,
|
||||
or both.
|
||||
|
@ -1806,6 +1977,12 @@ The result includes the following fields:
|
|||
* 'tidy_cross_cluster_revoked_certs': the value of this parameter when initiating the tidy operation
|
||||
* 'cross_revoked_cert_deleted_count': the number of cross-cluster revoked certificate entries deleted
|
||||
* 'revocation_queue_safety_buffer': the value of this parameter when initiating the tidy operation
|
||||
* 'tidy_acme': the value of this parameter when initiating the tidy operation
|
||||
* 'acme_account_safety_buffer': the value of this parameter when initiating the tidy operation
|
||||
* 'total_acme_account_count': the total number of acme accounts in the list to be iterated over
|
||||
* 'acme_account_deleted_count': the number of revoked acme accounts deleted during the operation
|
||||
* 'acme_account_revoked_count': the number of acme accounts revoked during the operation
|
||||
* 'acme_orders_deleted_count': the number of acme orders deleted during the operation
|
||||
`
|
||||
|
||||
const pathConfigAutoTidySyn = `
|
||||
|
@ -1821,3 +1998,26 @@ controls the frequency of auto-tidy execution).
|
|||
Once enabled, a tidy operation will be kicked off automatically, as if it
|
||||
were executed with the posted configuration.
|
||||
`
|
||||
|
||||
func getTidyConfigData(config tidyConfig) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
// This map is in the same order as tidyConfig to ensure that all fields are accounted for
|
||||
"enabled": config.Enabled,
|
||||
"interval_duration": int(config.Interval / time.Second),
|
||||
"tidy_cert_store": config.CertStore,
|
||||
"tidy_revoked_certs": config.RevokedCerts,
|
||||
"tidy_revoked_cert_issuer_associations": config.IssuerAssocs,
|
||||
"tidy_expired_issuers": config.ExpiredIssuers,
|
||||
"tidy_move_legacy_ca_bundle": config.BackupBundle,
|
||||
"tidy_acme": config.TidyAcme,
|
||||
"safety_buffer": int(config.SafetyBuffer / time.Second),
|
||||
"issuer_safety_buffer": int(config.IssuerSafetyBuffer / time.Second),
|
||||
"acme_account_safety_buffer": int(config.AcmeAccountSafetyBuffer / time.Second),
|
||||
"pause_duration": config.PauseDuration.String(),
|
||||
"publish_stored_certificate_count_metrics": config.PublishMetrics,
|
||||
"maintain_stored_certificate_counts": config.MaintainCount,
|
||||
"tidy_revocation_queue": config.RevocationQueue,
|
||||
"revocation_queue_safety_buffer": int(config.QueueSafetyBuffer / time.Second),
|
||||
"tidy_cross_cluster_revoked_certs": config.CrossRevokedCerts,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -408,6 +408,7 @@ func TestTidyIssuerConfig(t *testing.T) {
|
|||
defaultConfigMap["safety_buffer"] = int(time.Duration(defaultConfigMap["safety_buffer"].(float64)) / time.Second)
|
||||
defaultConfigMap["pause_duration"] = time.Duration(defaultConfigMap["pause_duration"].(float64)).String()
|
||||
defaultConfigMap["revocation_queue_safety_buffer"] = int(time.Duration(defaultConfigMap["revocation_queue_safety_buffer"].(float64)) / time.Second)
|
||||
defaultConfigMap["acme_account_safety_buffer"] = int(time.Duration(defaultConfigMap["acme_account_safety_buffer"].(float64)) / time.Second)
|
||||
|
||||
require.Equal(t, defaultConfigMap, resp.Data)
|
||||
|
||||
|
|
Loading…
Reference in New Issue