From 49fd772fcc3ebb82a81a44835388025f9e8949e9 Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Fri, 19 Aug 2022 11:43:44 -0400 Subject: [PATCH] Add per-issuer AIA URI information to PKI secrets engine (#16563) * Add per-issuer AIA URI information Per discussion on GitHub with @maxb, this allows issuers to have their own copy of AIA URIs. Because each issuer has its own URLs (for CA and CRL access), its necessary to mint their issued certs pointing to the correct issuer and not to the global default issuer. For anyone using multiple issuers within a mount, this change allows the issuer to point back to itself via leaf's AIA info. Signed-off-by: Alexander Scheel * Add changelog Signed-off-by: Alexander Scheel * Add documentation on per-issuer AIA info Also add it to the considerations page as something to watch out for. Signed-off-by: Alexander Scheel * Add tests for per-issuer AIA information Signed-off-by: Alexander Scheel * Refactor AIA setting on the issuer This introduces a common helper per Steve's suggestion. Signed-off-by: Alexander Scheel * Clarify error messages w.r.t. AIA naming Signed-off-by: Alexander Scheel * Clarify error messages regarding AIA URLs This clarifies which request parameter the invalid URL is contained in, disambiguating the sometimes ambiguous usage of AIA, per suggestion by Max. Signed-off-by: Alexander Scheel * Rename getURLs -> getGlobalAIAURLs Signed-off-by: Alexander Scheel * Correct AIA acronym expansion word orders Signed-off-by: Alexander Scheel * Fix bad comment suggesting re-generating roots Signed-off-by: Alexander Scheel * Add two entries to URL tests Signed-off-by: Alexander Scheel Signed-off-by: Alexander Scheel --- builtin/logical/pki/backend_test.go | 75 ++++++++++ builtin/logical/pki/cert_util.go | 9 +- builtin/logical/pki/path_config_urls.go | 12 +- builtin/logical/pki/path_fetch_issuers.go | 131 ++++++++++++++++++ builtin/logical/pki/path_intermediate.go | 4 +- builtin/logical/pki/path_manage_issuers.go | 4 +- builtin/logical/pki/path_root.go | 6 +- builtin/logical/pki/storage.go | 14 ++ builtin/logical/pki/util.go | 14 ++ changelog/16563.txt | 3 + website/content/api-docs/secret/pki.mdx | 35 ++++- .../docs/secrets/pki/considerations.mdx | 6 + 12 files changed, 295 insertions(+), 18 deletions(-) create mode 100644 changelog/16563.txt diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index 3c339119f..d584111db 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -4922,6 +4922,81 @@ AwEHoUQDQgAE57NX8bR/nDoW8yRgLswoXBQcjHrdyfuHS0gPwki6BNnfunUzryVb require.Equal(t, len(importedIssuers), 0) } +func TestPerIssuerAIA(t *testing.T) { + t.Parallel() + b, s := createBackendWithStorage(t) + + // Generating a root without anything should not have AIAs. + resp, err := CBWrite(b, s, "root/generate/internal", map[string]interface{}{ + "common_name": "root example.com", + "issuer_name": "root", + "key_type": "ec", + }) + require.NoError(t, err) + require.NotNil(t, resp) + rootCert := parseCert(t, resp.Data["certificate"].(string)) + require.Empty(t, rootCert.OCSPServer) + require.Empty(t, rootCert.IssuingCertificateURL) + require.Empty(t, rootCert.CRLDistributionPoints) + + // Set some local URLs on the issuer. + _, err = CBWrite(b, s, "issuer/default", map[string]interface{}{ + "issuing_certificates": []string{"https://google.com"}, + }) + require.NoError(t, err) + + _, err = CBWrite(b, s, "roles/testing", map[string]interface{}{ + "allow_any_name": true, + "ttl": "85s", + "key_type": "ec", + }) + require.NoError(t, err) + + // Issue something with this re-configured issuer. + resp, err = CBWrite(b, s, "issuer/default/issue/testing", map[string]interface{}{ + "common_name": "localhost.com", + }) + require.NoError(t, err) + require.NotNil(t, resp) + leafCert := parseCert(t, resp.Data["certificate"].(string)) + require.Empty(t, leafCert.OCSPServer) + require.Equal(t, leafCert.IssuingCertificateURL, []string{"https://google.com"}) + require.Empty(t, leafCert.CRLDistributionPoints) + + // Set global URLs and ensure they don't appear on this issuer's leaf. + _, err = CBWrite(b, s, "config/urls", map[string]interface{}{ + "issuing_certificates": []string{"https://example.com/ca", "https://backup.example.com/ca"}, + "crl_distribution_points": []string{"https://example.com/crl", "https://backup.example.com/crl"}, + "ocsp_servers": []string{"https://example.com/ocsp", "https://backup.example.com/ocsp"}, + }) + require.NoError(t, err) + resp, err = CBWrite(b, s, "issuer/default/issue/testing", map[string]interface{}{ + "common_name": "localhost.com", + }) + require.NoError(t, err) + require.NotNil(t, resp) + leafCert = parseCert(t, resp.Data["certificate"].(string)) + require.Empty(t, leafCert.OCSPServer) + require.Equal(t, leafCert.IssuingCertificateURL, []string{"https://google.com"}) + require.Empty(t, leafCert.CRLDistributionPoints) + + // Now come back and remove the local modifications and ensure we get + // the defaults again. + _, err = CBPatch(b, s, "issuer/default", map[string]interface{}{ + "issuing_certificates": []string{}, + }) + require.NoError(t, err) + resp, err = CBWrite(b, s, "issuer/default/issue/testing", map[string]interface{}{ + "common_name": "localhost.com", + }) + require.NoError(t, err) + require.NotNil(t, resp) + leafCert = parseCert(t, resp.Data["certificate"].(string)) + require.Equal(t, leafCert.IssuingCertificateURL, []string{"https://example.com/ca", "https://backup.example.com/ca"}) + require.Equal(t, leafCert.OCSPServer, []string{"https://example.com/ocsp", "https://backup.example.com/ocsp"}) + require.Equal(t, leafCert.CRLDistributionPoints, []string{"https://example.com/crl", "https://backup.example.com/crl"}) +} + var ( initTest sync.Once rsaCAKey string diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index 142cf9b8e..019641f4f 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -141,7 +141,7 @@ func (sc *storageContext) fetchCAInfoByIssuerId(issuerId issuerID, usage issuerU RevocationSigAlg: entry.RevocationSigAlg, } - entries, err := getURLs(sc.Context, sc.Storage) + entries, err := entry.GetAIAURLs(sc) if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)} } @@ -674,8 +674,9 @@ func generateCert(sc *storageContext, data.Params.PermittedDNSDomains = input.apiData.Get("permitted_dns_domains").([]string) if data.SigningBundle == nil { - // Generating a self-signed root certificate - entries, err := getURLs(ctx, sc.Storage) + // Generating a self-signed root certificate. Since we have no + // issuer entry yet, we default to the global URLs. + entries, err := getGlobalAIAURLs(ctx, sc.Storage) if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch URL information: %v", err)} } @@ -1395,7 +1396,7 @@ func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAIn return creation, nil } - // This will have been read in from the getURLs function + // This will have been read in from the getGlobalAIAURLs function creation.Params.URLs = caSign.URLs // If the max path length in the role is not nil, it was specified at diff --git a/builtin/logical/pki/path_config_urls.go b/builtin/logical/pki/path_config_urls.go index 616259873..830ca34ad 100644 --- a/builtin/logical/pki/path_config_urls.go +++ b/builtin/logical/pki/path_config_urls.go @@ -57,7 +57,7 @@ func validateURLs(urls []string) string { return "" } -func getURLs(ctx context.Context, storage logical.Storage) (*certutil.URLEntries, error) { +func getGlobalAIAURLs(ctx context.Context, storage logical.Storage) (*certutil.URLEntries, error) { entry, err := storage.Get(ctx, "urls") if err != nil { return nil, err @@ -98,7 +98,7 @@ func writeURLs(ctx context.Context, storage logical.Storage, entries *certutil.U } func (b *backend) pathReadURL(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { - entries, err := getURLs(ctx, req.Storage) + entries, err := getGlobalAIAURLs(ctx, req.Storage) if err != nil { return nil, err } @@ -115,7 +115,7 @@ func (b *backend) pathReadURL(ctx context.Context, req *logical.Request, _ *fram } func (b *backend) pathWriteURL(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - entries, err := getURLs(ctx, req.Storage) + entries, err := getGlobalAIAURLs(ctx, req.Storage) if err != nil { return nil, err } @@ -124,21 +124,21 @@ func (b *backend) pathWriteURL(ctx context.Context, req *logical.Request, data * entries.IssuingCertificates = urlsInt.([]string) if badURL := validateURLs(entries.IssuingCertificates); badURL != "" { return logical.ErrorResponse(fmt.Sprintf( - "invalid URL found in issuing certificates: %s", badURL)), nil + "invalid URL found in Authority Information Access (AIA) parameter issuing_certificates: %s", badURL)), nil } } if urlsInt, ok := data.GetOk("crl_distribution_points"); ok { entries.CRLDistributionPoints = urlsInt.([]string) if badURL := validateURLs(entries.CRLDistributionPoints); badURL != "" { return logical.ErrorResponse(fmt.Sprintf( - "invalid URL found in CRL distribution points: %s", badURL)), nil + "invalid URL found in Authority Information Access (AIA) parameter crl_distribution_points: %s", badURL)), nil } } if urlsInt, ok := data.GetOk("ocsp_servers"); ok { entries.OCSPServers = urlsInt.([]string) if badURL := validateURLs(entries.OCSPServers); badURL != "" { return logical.ErrorResponse(fmt.Sprintf( - "invalid URL found in OCSP servers: %s", badURL)), nil + "invalid URL found in Authority Information Access (AIA) parameter ocsp_servers: %s", badURL)), nil } } diff --git a/builtin/logical/pki/path_fetch_issuers.go b/builtin/logical/pki/path_fetch_issuers.go index 706657ec5..2e7a818fb 100644 --- a/builtin/logical/pki/path_fetch_issuers.go +++ b/builtin/logical/pki/path_fetch_issuers.go @@ -119,6 +119,21 @@ which may not be known at modification time (such as with PKCS#11 managed RSA keys).`, Default: "", } + fields["issuing_certificates"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the issuing certificate attribute. See also RFC 5280 Section 4.2.2.1.`, + } + fields["crl_distribution_points"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the CRL distribution points attribute. See also RFC 5280 Section 4.2.1.13.`, + } + fields["ocsp_servers"] = &framework.FieldSchema{ + Type: framework.TypeCommaStringSlice, + Description: `Comma-separated list of URLs to be used +for the OCSP servers attribute. See also RFC 5280 Section 4.2.2.1.`, + } return &framework.Path{ // Returns a JSON entry. @@ -208,6 +223,9 @@ func respondReadIssuer(issuer *issuerEntry) (*logical.Response, error) { "usage": issuer.Usage.Names(), "revocation_signature_algorithm": revSigAlgStr, "revoked": issuer.Revoked, + "issuing_certificates": []string{}, + "crl_distribution_points": []string{}, + "ocsp_servers": []string{}, } if issuer.Revoked { @@ -215,6 +233,12 @@ func respondReadIssuer(issuer *issuerEntry) (*logical.Response, error) { data["revocation_time_rfc3339"] = issuer.RevocationTimeUTC.Format(time.RFC3339Nano) } + if issuer.AIAURIs != nil { + data["issuing_certificates"] = issuer.AIAURIs.IssuingCertificates + data["crl_distribution_points"] = issuer.AIAURIs.CRLDistributionPoints + data["ocsp_servers"] = issuer.AIAURIs.OCSPServers + } + return &logical.Response{ Data: data, }, nil @@ -302,6 +326,20 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da return nil, err } + // AIA access changes + issuerCertificates := data.Get("issuing_certificates").([]string) + if badURL := validateURLs(issuerCertificates); badURL != "" { + return logical.ErrorResponse(fmt.Sprintf("invalid URL found in Authority Information Access (AIA) parameter issuing_certificates: %s", badURL)), nil + } + crlDistributionPoints := data.Get("crl_distribution_points").([]string) + if badURL := validateURLs(crlDistributionPoints); badURL != "" { + return logical.ErrorResponse(fmt.Sprintf("invalid URL found in Authority Information Access (AIA) parameter crl_distribution_points: %s", badURL)), nil + } + ocspServers := data.Get("ocsp_servers").([]string) + if badURL := validateURLs(ocspServers); badURL != "" { + return logical.ErrorResponse(fmt.Sprintf("invalid URL found in Authority Information Access (AIA) parameter ocsp_servers: %s", badURL)), nil + } + modified := false var oldName string @@ -331,6 +369,49 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da modified = true } + if issuer.AIAURIs == nil && (len(issuerCertificates) > 0 || len(crlDistributionPoints) > 0 || len(ocspServers) > 0) { + issuer.AIAURIs = &certutil.URLEntries{} + } + if issuer.AIAURIs != nil { + // Associative mapping from data source to destination on the + // backing issuer object. + type aiaPair struct { + Source *[]string + Dest *[]string + } + pairs := []aiaPair{ + { + Source: &issuerCertificates, + Dest: &issuer.AIAURIs.IssuingCertificates, + }, + { + Source: &crlDistributionPoints, + Dest: &issuer.AIAURIs.CRLDistributionPoints, + }, + { + Source: &ocspServers, + Dest: &issuer.AIAURIs.OCSPServers, + }, + } + + // For each pair, if it is different on the object, update it. + for _, pair := range pairs { + if isStringArrayDifferent(*pair.Source, *pair.Dest) { + *pair.Dest = *pair.Source + modified = true + } + } + + // If no AIA URLs exist on the issuer, set the AIA URLs entry to nil + // to ease usage later. + if len(issuer.AIAURIs.IssuingCertificates) == 0 && len(issuer.AIAURIs.CRLDistributionPoints) == 0 && len(issuer.AIAURIs.OCSPServers) == 0 { + issuer.AIAURIs = nil + } + } + + // Updating the chain should be the last modification as there's a chance + // it'll write it out to disk for us. We'd hate to then modify the issuer + // again and write it a second time. var updateChain bool var constructedChain []issuerID for index, newPathRef := range newPath { @@ -511,6 +592,56 @@ func (b *backend) pathPatchIssuer(ctx context.Context, req *logical.Request, dat } } + // AIA access changes. + if issuer.AIAURIs == nil { + issuer.AIAURIs = &certutil.URLEntries{} + } + + // Associative mapping from data source to destination on the + // backing issuer object. For PATCH requests, we use the source + // data parameter as we still need to validate them and process + // it into a string list. + type aiaPair struct { + Source string + Dest *[]string + } + pairs := []aiaPair{ + { + Source: "issuing_certificates", + Dest: &issuer.AIAURIs.IssuingCertificates, + }, + { + Source: "crl_distribution_points", + Dest: &issuer.AIAURIs.CRLDistributionPoints, + }, + { + Source: "ocsp_servers", + Dest: &issuer.AIAURIs.OCSPServers, + }, + } + + // For each pair, if it is different on the object, update it. + for _, pair := range pairs { + rawURLsValue, ok := data.GetOk(pair.Source) + if ok { + urlsValue := rawURLsValue.([]string) + if badURL := validateURLs(urlsValue); badURL != "" { + return logical.ErrorResponse(fmt.Sprintf("invalid URL found in Authority Information Access (AIA) parameter %v: %s", pair.Source, badURL)), nil + } + + if isStringArrayDifferent(urlsValue, *pair.Dest) { + modified = true + *pair.Dest = urlsValue + } + } + } + + // If no AIA URLs exist on the issuer, set the AIA URLs entry to nil to + // ease usage later. + if len(issuer.AIAURIs.IssuingCertificates) == 0 && len(issuer.AIAURIs.CRLDistributionPoints) == 0 && len(issuer.AIAURIs.OCSPServers) == 0 { + issuer.AIAURIs = nil + } + // Manual Chain Changes newPathData, ok := data.GetOk("manual_chain") if ok { diff --git a/builtin/logical/pki/path_intermediate.go b/builtin/logical/pki/path_intermediate.go index b33c22f36..f634cde74 100644 --- a/builtin/logical/pki/path_intermediate.go +++ b/builtin/logical/pki/path_intermediate.go @@ -114,12 +114,12 @@ func (b *backend) pathGenerateIntermediate(ctx context.Context, req *logical.Req Data: map[string]interface{}{}, } - entries, err := getURLs(ctx, req.Storage) + entries, err := getGlobalAIAURLs(ctx, req.Storage) if err == nil && len(entries.OCSPServers) == 0 && len(entries.IssuingCertificates) == 0 && len(entries.CRLDistributionPoints) == 0 { // If the operator hasn't configured any of the URLs prior to // generating this issuer, we should add a warning to the response, // informing them they might want to do so and re-generate the issuer. - resp.AddWarning("This mount hasn't configured any authority access information fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls with this information.") + resp.AddWarning("This mount hasn't configured any authority information access (AIA) fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls or the newly generated issuer with this information. Since this certificate is an intermediate, it might be useful to regenerate this certificate after fixing this problem for the root mount.") } switch format { diff --git a/builtin/logical/pki/path_manage_issuers.go b/builtin/logical/pki/path_manage_issuers.go index 46dd24eb3..a22df03a4 100644 --- a/builtin/logical/pki/path_manage_issuers.go +++ b/builtin/logical/pki/path_manage_issuers.go @@ -300,8 +300,8 @@ func (b *backend) pathImportIssuers(ctx context.Context, req *logical.Request, d // Also while we're here, we should let the user know the next steps. // In particular, if there's no default AIA URLs configuration, we should // tell the user that's probably next. - if entries, err := getURLs(ctx, req.Storage); err == nil && len(entries.IssuingCertificates) == 0 && len(entries.CRLDistributionPoints) == 0 && len(entries.OCSPServers) == 0 { - response.AddWarning("This mount hasn't configured any authority access information fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls with this information.") + if entries, err := getGlobalAIAURLs(ctx, req.Storage); err == nil && len(entries.IssuingCertificates) == 0 && len(entries.CRLDistributionPoints) == 0 && len(entries.OCSPServers) == 0 { + response.AddWarning("This mount hasn't configured any authority information access (AIA) fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls or the newly generated issuer with this information.") } return response, nil diff --git a/builtin/logical/pki/path_root.go b/builtin/logical/pki/path_root.go index 7ef6151b8..f39af232a 100644 --- a/builtin/logical/pki/path_root.go +++ b/builtin/logical/pki/path_root.go @@ -185,7 +185,7 @@ func (b *backend) pathCAGenerateRoot(ctx context.Context, req *logical.Request, // If the operator hasn't configured any of the URLs prior to // generating this issuer, we should add a warning to the response, // informing them they might want to do so prior to issuing leaves. - resp.AddWarning("This mount hasn't configured any authority access information fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls with this information.") + resp.AddWarning("This mount hasn't configured any authority information access (AIA) fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls or the newly generated issuer with this information.") } switch format { @@ -407,8 +407,8 @@ func (b *backend) pathIssuerSignIntermediate(ctx context.Context, req *logical.R if len(parsedBundle.Certificate.OCSPServer) == 0 && len(parsedBundle.Certificate.IssuingCertificateURL) == 0 && len(parsedBundle.Certificate.CRLDistributionPoints) == 0 { // If the operator hasn't configured any of the URLs prior to // generating this issuer, we should add a warning to the response, - // informing them they might want to do so and re-generate the issuer. - resp.AddWarning("This mount hasn't configured any authority access information fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls with this information.") + // informing them they might want to do so prior to issuing leaves. + resp.AddWarning("This mount hasn't configured any authority information access (AIA) fields; this may make it harder for systems to find missing certificates in the chain or to validate revocation status of certificates. Consider updating /config/urls or the newly generated issuer with this information.") } caChain := append([]string{cb.Certificate}, cb.CAChain...) diff --git a/builtin/logical/pki/storage.go b/builtin/logical/pki/storage.go index ff4c684db..ad9ac81c0 100644 --- a/builtin/logical/pki/storage.go +++ b/builtin/logical/pki/storage.go @@ -150,6 +150,7 @@ type issuerEntry struct { Revoked bool `json:"revoked"` RevocationTime int64 `json:"revocation_time"` RevocationTimeUTC time.Time `json:"revocation_time_utc"` + AIAURIs *certutil.URLEntries `json:"aia_uris,omitempty"` } type localCRLConfigEntry struct { @@ -477,6 +478,19 @@ func (i issuerEntry) CanMaybeSignWithAlgo(algo x509.SignatureAlgorithm) error { return fmt.Errorf("unable to use issuer of type %v to sign with %v key type", cert.PublicKeyAlgorithm.String(), algo.String()) } +func (i issuerEntry) GetAIAURLs(sc *storageContext) (urls *certutil.URLEntries, err error) { + // Default to the per-issuer AIA URLs. + urls = i.AIAURIs + + // If none are set (either due to a nil entry or because no URLs have + // been provided), fall back to the global AIA URL config. + if urls == nil || (len(urls.IssuingCertificates) == 0 && len(urls.CRLDistributionPoints) == 0 && len(urls.OCSPServers) == 0) { + urls, err = getGlobalAIAURLs(sc.Context, sc.Storage) + } + + return urls, err +} + func (sc *storageContext) listIssuers() ([]issuerID, error) { strList, err := sc.Storage.List(sc.Context, issuerPrefix) if err != nil { diff --git a/builtin/logical/pki/util.go b/builtin/logical/pki/util.go index 9e774a43a..feabc2855 100644 --- a/builtin/logical/pki/util.go +++ b/builtin/logical/pki/util.go @@ -208,3 +208,17 @@ func extractRef(data *framework.FieldData, paramName string) string { } return value } + +func isStringArrayDifferent(a, b []string) bool { + if len(a) != len(b) { + return true + } + + for i, v := range a { + if v != b[i] { + return true + } + } + + return false +} diff --git a/changelog/16563.txt b/changelog/16563.txt new file mode 100644 index 000000000..e5ff2758a --- /dev/null +++ b/changelog/16563.txt @@ -0,0 +1,3 @@ +```release-note:improvement +secrets/pki: Add support for per-issuer Authority Information Access (AIA) URLs +``` diff --git a/website/content/api-docs/secret/pki.mdx b/website/content/api-docs/secret/pki.mdx index 6977aafb5..a7d378ec3 100644 --- a/website/content/api-docs/secret/pki.mdx +++ b/website/content/api-docs/secret/pki.mdx @@ -1995,6 +1995,29 @@ do so, import a new issuer and a new `issuer_id` will be assigned. This most commonly needs to be modified when using PKCS#11 managed keys with the `CKM_RSA_PKCS_PSS` mechanism type. +- `issuing_certificates` `(array: nil)` - Specifies the URL values for + the Issuing Certificate field. This can be an array or a comma-separated + string list. See also [RFC 5280 Section 4.2.2.1](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.2.1) + for information about the Authority Information Access field. + +- `crl_distribution_points` `(array: nil)` - Specifies the URL values + for the CRL Distribution Points field. This can be an array or a + comma-separated string list. See also [RFC 5280 Section 4.2.1.13](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13) + for information about the CRL Distribution Points field. + +~> Note: When multiple Performance Replication clusters are enabled, each + cluster will have its own CRL. Additionally, when multiple issuers are + in use under a single mount, each issuer will also have its own CRL + distribution point. These separate CRLs should either be aggregated into a + single CRL (externally; as Vault does not support this functionality) + or multiple `crl_distribution_points` should be specified here, pointing + to each cluster and issuer. + +- `ocsp_servers` `(array: nil)` - Specifies the URL values for the OCSP + Servers field. This can be an array or a comma-separated string list. See also + [RFC 5280 Section 4.2.2.1](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.2.1) + for information about the Authority Information Access field. + #### Sample Payload ```json @@ -2028,7 +2051,11 @@ $ curl \ "key_id": "baadd98d-ec5a-66ac-06b7-dfc91c02c9cf", "leaf_not_after_behavior": "truncate", "manual_chain": null, - "usage": "read-only,issuing-certificates,crl-signing" + "usage": "read-only,issuing-certificates,crl-signing", + "revocation_signature_algorithm": "", + "issuing_certificates": ["", ""], + "crl_distribution_points": ["", ""], + "ocsp_servers": ["", ""] } } ``` @@ -2713,6 +2740,12 @@ parameter. | :----- | :----------------- | | `POST` | `/pki/config/urls` | +~> **Note**: When using multiple issuers within the same mount, it is strongly + suggested to use the per-issuer AIA information instead of the global + AIA information. If any of the per-issuer AIA fields are set, the entire + issuer's preferences will be used instead. Otherwise, these fields are used + as a fallback. + #### Parameters - `issuing_certificates` `(array: nil)` - Specifies the URL values for diff --git a/website/content/docs/secrets/pki/considerations.mdx b/website/content/docs/secrets/pki/considerations.mdx index 02ca8088f..185ab2008 100644 --- a/website/content/docs/secrets/pki/considerations.mdx +++ b/website/content/docs/secrets/pki/considerations.mdx @@ -258,6 +258,12 @@ comma-separated string parameter. field separately, or the CRLs should be consolidated and served outside of Vault. +~> Note: When using multiple issuers in the same mount, it is suggested to use + the per-issuer AIA fields rather than the global (`/config/urls`) variant. + This is for correctness: these fields are used for chain building and + automatic CRL detection in certain applications. If they point to the wrong + issuer's information, these applications may break. + ## Automate Leaf Certificate Renewal As much as possible, for managing certificates for services at scale, it is