5a2ee4ca7a
* Add automatic tidy of expired issuers To aid PKI users like Consul, which periodically rotate intermediates, and provided a little more consistency with older versions of Vault which would silently (and dangerously!) replace the configured CA on root/intermediate generation, we introduce an automatic tidy of expired issuers. This includes a longer safety buffer (1 year) and logging of the relevant issuer information prior to deletion (certificate contents, key ID, and issuer ID/name) to allow admins to recover this value if desired, or perform further cleanup of keys. From my PoV, removal of the issuer is thus a relatively safe operation compared to keys (which I do not feel comfortable removing) as they can always be re-imported if desired. Additionally, this is an opt-in tidy operation, not enabled by default. Lastly, most major performance penalties comes with lots of issuers within the mount, not as much large numbers of keys (as only new issuer creation/import operations are affected, unlike LIST /issuers which is a public, unauthenticated endpoint). Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add changelog entry Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add test for tidy Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add docs on tidy of issuers Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Restructure logging Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add missing fields to expected tidy output Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
496 lines
16 KiB
Go
496 lines
16 KiB
Go
package pki
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
)
|
|
|
|
const (
|
|
issuerRefParam = "issuer_ref"
|
|
keyNameParam = "key_name"
|
|
keyRefParam = "key_ref"
|
|
keyIdParam = "key_id"
|
|
keyTypeParam = "key_type"
|
|
keyBitsParam = "key_bits"
|
|
)
|
|
|
|
// addIssueAndSignCommonFields adds fields common to both CA and non-CA issuing
|
|
// and signing
|
|
func addIssueAndSignCommonFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields["exclude_cn_from_sans"] = &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Default: false,
|
|
Description: `If true, the Common Name will not be
|
|
included in DNS or Email Subject Alternate Names.
|
|
Defaults to false (CN is included).`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "Exclude Common Name from Subject Alternative Names (SANs)",
|
|
},
|
|
}
|
|
|
|
fields["format"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Default: "pem",
|
|
Description: `Format for returned data. Can be "pem", "der",
|
|
or "pem_bundle". If "pem_bundle", any private
|
|
key and issuing cert will be appended to the
|
|
certificate pem. If "der", the value will be
|
|
base64 encoded. Defaults to "pem".`,
|
|
AllowedValues: []interface{}{"pem", "der", "pem_bundle"},
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Value: "pem",
|
|
},
|
|
}
|
|
|
|
fields["private_key_format"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Default: "der",
|
|
Description: `Format for the returned private key.
|
|
Generally the default will be controlled by the "format"
|
|
parameter as either base64-encoded DER or PEM-encoded DER.
|
|
However, this can be set to "pkcs8" to have the returned
|
|
private key contain base64-encoded pkcs8 or PEM-encoded
|
|
pkcs8 instead. Defaults to "der".`,
|
|
AllowedValues: []interface{}{"", "der", "pem", "pkcs8"},
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Value: "der",
|
|
},
|
|
}
|
|
|
|
fields["ip_sans"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `The requested IP SANs, if any, in a
|
|
comma-delimited list`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "IP Subject Alternative Names (SANs)",
|
|
},
|
|
}
|
|
|
|
fields["uri_sans"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `The requested URI SANs, if any, in a
|
|
comma-delimited list.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "URI Subject Alternative Names (SANs)",
|
|
},
|
|
}
|
|
|
|
fields["other_sans"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `Requested other SANs, in an array with the format
|
|
<oid>;UTF8:<utf8 string value> for each entry.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "Other SANs",
|
|
},
|
|
}
|
|
|
|
return fields
|
|
}
|
|
|
|
// addNonCACommonFields adds fields with help text specific to non-CA
|
|
// certificate issuing and signing
|
|
func addNonCACommonFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields = addIssueAndSignCommonFields(fields)
|
|
|
|
fields["role"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The desired role with configuration for this
|
|
request`,
|
|
}
|
|
|
|
fields["common_name"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The requested common name; if you want more than
|
|
one, specify the alternative names in the
|
|
alt_names map. If email protection is enabled
|
|
in the role, this may be an email address.`,
|
|
}
|
|
|
|
fields["alt_names"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The requested Subject Alternative Names, if any,
|
|
in a comma-delimited list. If email protection
|
|
is enabled for the role, this may contain
|
|
email addresses.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "DNS/Email Subject Alternative Names (SANs)",
|
|
},
|
|
}
|
|
|
|
fields["serial_number"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The Subject's requested serial number, if any.
|
|
See RFC 4519 Section 2.31 'serialNumber' for a description of this field.
|
|
If you want more than one, specify alternative names in the alt_names
|
|
map using OID 2.5.4.5. This has no impact on the final certificate's
|
|
Serial Number field.`,
|
|
}
|
|
|
|
fields["ttl"] = &framework.FieldSchema{
|
|
Type: framework.TypeDurationSecond,
|
|
Description: `The requested Time To Live for the certificate;
|
|
sets the expiration date. If not specified
|
|
the role default, backend default, or system
|
|
default TTL is used, in that order. Cannot
|
|
be larger than the role max TTL.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "TTL",
|
|
},
|
|
}
|
|
|
|
fields["not_after"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Set the not after field of the certificate with specified date value.
|
|
The value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ`,
|
|
}
|
|
|
|
fields["remove_roots_from_chain"] = &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Default: false,
|
|
Description: `Whether or not to remove self-signed CA certificates in the output
|
|
of the ca_chain field.`,
|
|
}
|
|
|
|
fields = addIssuerRefField(fields)
|
|
|
|
return fields
|
|
}
|
|
|
|
// addCACommonFields adds fields with help text specific to CA
|
|
// certificate issuing and signing
|
|
func addCACommonFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields = addIssueAndSignCommonFields(fields)
|
|
|
|
fields["alt_names"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The requested Subject Alternative Names, if any,
|
|
in a comma-delimited list. May contain both
|
|
DNS names and email addresses.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "DNS/Email Subject Alternative Names (SANs)",
|
|
},
|
|
}
|
|
|
|
fields["common_name"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The requested common name; if you want more than
|
|
one, specify the alternative names in the alt_names
|
|
map. If not specified when signing, the common
|
|
name will be taken from the CSR; other names
|
|
must still be specified in alt_names or ip_sans.`,
|
|
}
|
|
|
|
fields["ttl"] = &framework.FieldSchema{
|
|
Type: framework.TypeDurationSecond,
|
|
Description: `The requested Time To Live for the certificate;
|
|
sets the expiration date. If not specified
|
|
the role default, backend default, or system
|
|
default TTL is used, in that order. Cannot
|
|
be larger than the mount max TTL. Note:
|
|
this only has an effect when generating
|
|
a CA cert or signing a CA cert, not when
|
|
generating a CSR for an intermediate CA.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "TTL",
|
|
},
|
|
}
|
|
|
|
fields["ou"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `If set, OU (OrganizationalUnit) will be set to
|
|
this value.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "OU (Organizational Unit)",
|
|
},
|
|
}
|
|
|
|
fields["organization"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `If set, O (Organization) will be set to
|
|
this value.`,
|
|
}
|
|
|
|
fields["country"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `If set, Country will be set to
|
|
this value.`,
|
|
}
|
|
|
|
fields["locality"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `If set, Locality will be set to
|
|
this value.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "Locality/City",
|
|
},
|
|
}
|
|
|
|
fields["province"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `If set, Province will be set to
|
|
this value.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "Province/State",
|
|
},
|
|
}
|
|
|
|
fields["street_address"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `If set, Street Address will be set to
|
|
this value.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "Street Address",
|
|
},
|
|
}
|
|
|
|
fields["postal_code"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `If set, Postal Code will be set to
|
|
this value.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "Postal Code",
|
|
},
|
|
}
|
|
|
|
fields["serial_number"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The Subject's requested serial number, if any.
|
|
See RFC 4519 Section 2.31 'serialNumber' for a description of this field.
|
|
If you want more than one, specify alternative names in the alt_names
|
|
map using OID 2.5.4.5. This has no impact on the final certificate's
|
|
Serial Number field.`,
|
|
}
|
|
|
|
fields["not_after"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Set the not after field of the certificate with specified date value.
|
|
The value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ`,
|
|
}
|
|
|
|
fields["not_before_duration"] = &framework.FieldSchema{
|
|
Type: framework.TypeDurationSecond,
|
|
Default: 30,
|
|
Description: `The duration before now which the certificate needs to be backdated by.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Value: 30,
|
|
},
|
|
}
|
|
|
|
return fields
|
|
}
|
|
|
|
// addCAKeyGenerationFields adds fields with help text specific to CA key
|
|
// generation and exporting
|
|
func addCAKeyGenerationFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields["exported"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Must be "internal", "exported" or "kms". If set to
|
|
"exported", the generated private key will be
|
|
returned. This is your *only* chance to retrieve
|
|
the private key!`,
|
|
AllowedValues: []interface{}{"internal", "external", "kms"},
|
|
}
|
|
|
|
fields["managed_key_name"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The name of the managed key to use when the exported
|
|
type is kms. When kms type is the key type, this field or managed_key_id
|
|
is required. Ignored for other types.`,
|
|
}
|
|
|
|
fields["managed_key_id"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `The name of the managed key to use when the exported
|
|
type is kms. When kms type is the key type, this field or managed_key_name
|
|
is required. Ignored for other types.`,
|
|
}
|
|
|
|
fields["key_bits"] = &framework.FieldSchema{
|
|
Type: framework.TypeInt,
|
|
Default: 0,
|
|
Description: `The number of bits to use. Allowed values are
|
|
0 (universal default); with rsa key_type: 2048 (default), 3072, or
|
|
4096; with ec key_type: 224, 256 (default), 384, or 521; ignored with
|
|
ed25519.`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Value: 0,
|
|
},
|
|
}
|
|
|
|
fields["signature_bits"] = &framework.FieldSchema{
|
|
Type: framework.TypeInt,
|
|
Default: 0,
|
|
Description: `The number of bits to use in the signature
|
|
algorithm; accepts 256 for SHA-2-256, 384 for SHA-2-384, and 512 for
|
|
SHA-2-512. Defaults to 0 to automatically detect based on key length
|
|
(SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Value: 0,
|
|
},
|
|
}
|
|
|
|
fields["use_pss"] = &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Default: false,
|
|
Description: `Whether or not to use PSS signatures when using a
|
|
RSA key-type issuer. Defaults to false.`,
|
|
}
|
|
|
|
fields["key_type"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Default: "rsa",
|
|
Description: `The type of key to use; defaults to RSA. "rsa"
|
|
"ec" and "ed25519" are the only valid values.`,
|
|
AllowedValues: []interface{}{"rsa", "ec", "ed25519"},
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Value: "rsa",
|
|
},
|
|
}
|
|
|
|
fields = addKeyRefNameFields(fields)
|
|
|
|
return fields
|
|
}
|
|
|
|
// addCAIssueFields adds fields common to CA issuing, e.g. when returning
|
|
// an actual certificate
|
|
func addCAIssueFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields["max_path_length"] = &framework.FieldSchema{
|
|
Type: framework.TypeInt,
|
|
Default: -1,
|
|
Description: "The maximum allowable path length",
|
|
}
|
|
|
|
fields["permitted_dns_domains"] = &framework.FieldSchema{
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `Domains for which this certificate is allowed to sign or issue child certificates. If set, all DNS names (subject and alt) on child certs must be exact matches or subsets of the given domains (see https://tools.ietf.org/html/rfc5280#section-4.2.1.10).`,
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
Name: "Permitted DNS Domains",
|
|
},
|
|
}
|
|
|
|
fields = addIssuerNameField(fields)
|
|
|
|
return fields
|
|
}
|
|
|
|
func addIssuerRefNameFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields = addIssuerNameField(fields)
|
|
fields = addIssuerRefField(fields)
|
|
return fields
|
|
}
|
|
|
|
func addIssuerNameField(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields["issuer_name"] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Provide a name to the generated or existing issuer, the name
|
|
must be unique across all issuers and not be the reserved value 'default'`,
|
|
}
|
|
return fields
|
|
}
|
|
|
|
func addIssuerRefField(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields[issuerRefParam] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Reference to a existing issuer; either "default"
|
|
for the configured default issuer, an identifier or the name assigned
|
|
to the issuer.`,
|
|
Default: defaultRef,
|
|
}
|
|
return fields
|
|
}
|
|
|
|
func addKeyRefNameFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields = addKeyNameField(fields)
|
|
fields = addKeyRefField(fields)
|
|
return fields
|
|
}
|
|
|
|
func addKeyNameField(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields[keyNameParam] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Provide a name to the generated or existing key, the name
|
|
must be unique across all keys and not be the reserved value 'default'`,
|
|
}
|
|
|
|
return fields
|
|
}
|
|
|
|
func addKeyRefField(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields[keyRefParam] = &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: `Reference to a existing key; either "default"
|
|
for the configured default key, an identifier or the name assigned
|
|
to the key.`,
|
|
Default: defaultRef,
|
|
}
|
|
return fields
|
|
}
|
|
|
|
func addTidyFields(fields map[string]*framework.FieldSchema) map[string]*framework.FieldSchema {
|
|
fields["tidy_cert_store"] = &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Description: `Set to true to enable tidying up
|
|
the certificate store`,
|
|
}
|
|
|
|
fields["tidy_revocation_list"] = &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Description: `Deprecated; synonym for 'tidy_revoked_certs`,
|
|
}
|
|
|
|
fields["tidy_revoked_certs"] = &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Description: `Set to true to expire all revoked
|
|
and expired certificates, removing them both from the CRL and from storage. The
|
|
CRL will be rotated if this causes any values to be removed.`,
|
|
}
|
|
|
|
fields["tidy_revoked_cert_issuer_associations"] = &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Description: `Set to true to validate issuer associations
|
|
on revocation entries. This helps increase the performance of CRL building
|
|
and OCSP responses.`,
|
|
}
|
|
|
|
fields["tidy_expired_issuers"] = &framework.FieldSchema{
|
|
Type: framework.TypeBool,
|
|
Description: `Set to true to automatically remove expired issuers
|
|
past the issuer_safety_buffer. No keys will be removed as part of this
|
|
operation.`,
|
|
}
|
|
|
|
fields["safety_buffer"] = &framework.FieldSchema{
|
|
Type: framework.TypeDurationSecond,
|
|
Description: `The amount of extra time that must have passed
|
|
beyond certificate expiration before it is removed
|
|
from the backend storage and/or revocation list.
|
|
Defaults to 72 hours.`,
|
|
Default: int(defaultTidyConfig.SafetyBuffer / time.Second), // TypeDurationSecond currently requires defaults to be int
|
|
}
|
|
|
|
fields["issuer_safety_buffer"] = &framework.FieldSchema{
|
|
Type: framework.TypeDurationSecond,
|
|
Description: `The amount of extra time that must have passed
|
|
beyond issuer's expiration before it is removed
|
|
from the backend storage.
|
|
Defaults to 8760 hours (1 year).`,
|
|
Default: int(defaultTidyConfig.IssuerSafetyBuffer / 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
|
|
certificates. This allows operators to change the execution profile
|
|
of tidy to take consume less resources by slowing down how long it
|
|
takes to run. Note that the entire list of certificates will be
|
|
stored in memory during the entire tidy operation, but resources to
|
|
read/process/update existing entries will be spread out over a
|
|
greater period of time. By default this is zero seconds.`,
|
|
Default: "0s",
|
|
}
|
|
|
|
return fields
|
|
}
|