From 38de21468e23e7d10490c68e233dec9309074d07 Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Tue, 10 Jan 2023 09:51:37 -0500 Subject: [PATCH] Add cluster_aia_path templating variable (#18493) * Add cluster_aia_path templating variable Per discussion with maxb, allow using a non-Vault distribution point which may use an insecure transport for RFC 5280 compliance. Signed-off-by: Alexander Scheel * Address feedback from Max Co-authored-by: Max Bowsher Signed-off-by: Alexander Scheel Signed-off-by: Alexander Scheel Co-authored-by: Max Bowsher --- builtin/logical/pki/backend_test.go | 7 +-- builtin/logical/pki/path_config_cluster.go | 31 ++++++++++-- builtin/logical/pki/path_config_urls.go | 12 +++-- builtin/logical/pki/path_fetch_issuers.go | 10 ++-- builtin/logical/pki/storage.go | 10 ++-- website/content/api-docs/secret/pki.mdx | 59 +++++++++++++++------- 6 files changed, 92 insertions(+), 37 deletions(-) diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index c67999984..22b4cc5c4 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -5971,13 +5971,14 @@ func TestPKI_TemplatedAIAs(t *testing.T) { // Setting templated AIAs should succeed. _, err := CBWrite(b, s, "config/cluster", map[string]interface{}{ - "path": "http://localhost:8200/v1/pki", + "path": "http://localhost:8200/v1/pki", + "aia_path": "http://localhost:8200/cdn/pki", }) require.NoError(t, err) aiaData := map[string]interface{}{ "crl_distribution_points": "{{cluster_path}}/issuer/{{issuer_id}}/crl/der", - "issuing_certificates": "{{cluster_path}}/issuer/{{issuer_id}}/der", + "issuing_certificates": "{{cluster_aia_path}}/issuer/{{issuer_id}}/der", "ocsp_servers": "{{cluster_path}}/ocsp", "enable_templating": true, } @@ -6023,7 +6024,7 @@ func TestPKI_TemplatedAIAs(t *testing.T) { // Validate the AIA info is correctly templated. cert := parseCert(t, resp.Data["certificate"].(string)) require.Equal(t, cert.OCSPServer, []string{"http://localhost:8200/v1/pki/ocsp"}) - require.Equal(t, cert.IssuingCertificateURL, []string{"http://localhost:8200/v1/pki/issuer/" + issuerId + "/der"}) + require.Equal(t, cert.IssuingCertificateURL, []string{"http://localhost:8200/cdn/pki/issuer/" + issuerId + "/der"}) require.Equal(t, cert.CRLDistributionPoints, []string{"http://localhost:8200/v1/pki/issuer/" + issuerId + "/crl/der"}) // Modify our issuer to set custom AIAs: these URLs are bad. diff --git a/builtin/logical/pki/path_config_cluster.go b/builtin/logical/pki/path_config_cluster.go index 440dcc874..c887db7b7 100644 --- a/builtin/logical/pki/path_config_cluster.go +++ b/builtin/logical/pki/path_config_cluster.go @@ -26,6 +26,16 @@ including standby nodes, and need not always point to the active node. For example: https://pr1.vault.example.com:8200/v1/pki`, }, + "aia_path": { + Type: framework.TypeString, + Description: `Optional URI to this mount's AIA distribution +point; may refer to an external non-Vault responder. This is for resolving AIA +URLs and providing the {{cluster_aia_path}} template parameter and will not +be used for other purposes. As such, unlike path above, this could safely +be an insecure transit mechanism (like HTTP without TLS). + +For example: http://cdn.example.com/pr1/pki`, + }, }, Operations: map[logical.Operation]framework.OperationHandler{ @@ -51,7 +61,8 @@ func (b *backend) pathReadCluster(ctx context.Context, req *logical.Request, _ * resp := &logical.Response{ Data: map[string]interface{}{ - "path": cfg.Path, + "path": cfg.Path, + "aia_path": cfg.AIAPath, }, } @@ -65,9 +76,18 @@ func (b *backend) pathWriteCluster(ctx context.Context, req *logical.Request, da return nil, err } - cfg.Path = data.Get("path").(string) - if !govalidator.IsURL(cfg.Path) { - return nil, fmt.Errorf("invalid, non-URL path given to cluster: %v", cfg.Path) + if value, ok := data.GetOk("path"); ok { + cfg.Path = value.(string) + if !govalidator.IsURL(cfg.Path) { + return nil, fmt.Errorf("invalid, non-URL path given to cluster: %v", cfg.Path) + } + } + + if value, ok := data.GetOk("aia_path"); ok { + cfg.AIAPath = value.(string) + if !govalidator.IsURL(cfg.AIAPath) { + return nil, fmt.Errorf("invalid, non-URL aia_path given to cluster: %v", cfg.AIAPath) + } } if err := sc.writeClusterConfig(cfg); err != nil { @@ -76,7 +96,8 @@ func (b *backend) pathWriteCluster(ctx context.Context, req *logical.Request, da resp := &logical.Response{ Data: map[string]interface{}{ - "path": cfg.Path, + "path": cfg.Path, + "aia_path": cfg.AIAPath, }, } diff --git a/builtin/logical/pki/path_config_urls.go b/builtin/logical/pki/path_config_urls.go index 5b67ad080..5c81f9336 100644 --- a/builtin/logical/pki/path_config_urls.go +++ b/builtin/logical/pki/path_config_urls.go @@ -35,10 +35,12 @@ for the OCSP servers attribute. See also RFC 5280 Section 4.2.2.1.`, "enable_templating": { Type: framework.TypeBool, Description: `Whether or not to enabling templating of the -above AIA fields. When templating is enabled the special values '{{issuer_id}}' -and '{{cluster_path}}' are available, but the addresses are not checked for -URI validity until issuance time. This requires /config/cluster's path to be -set on all PR Secondary clusters.`, +above AIA fields. When templating is enabled the special values '{{issuer_id}}', +'{{cluster_path}}', and '{{cluster_aia_path}}' are available, but the addresses +are not checked for URI validity until issuance time. Using '{{cluster_path}}' +requires /config/cluster's 'path' member to be set on all PR Secondary clusters +and using '{{cluster_aia_path}}' requires /config/cluster's 'aia_path' member +to be set on all PR secondary clusters.`, Default: false, }, }, @@ -59,7 +61,7 @@ set on all PR Secondary clusters.`, func validateURLs(urls []string) string { for _, curr := range urls { - if !govalidator.IsURL(curr) || strings.Contains(curr, "{{issuer_id}}") || strings.Contains(curr, "{{cluster_path}}") { + if !govalidator.IsURL(curr) || strings.Contains(curr, "{{issuer_id}}") || strings.Contains(curr, "{{cluster_path}}") || strings.Contains(curr, "{{cluster_aia_path}}") { return curr } } diff --git a/builtin/logical/pki/path_fetch_issuers.go b/builtin/logical/pki/path_fetch_issuers.go index 58a97305f..f50be774d 100644 --- a/builtin/logical/pki/path_fetch_issuers.go +++ b/builtin/logical/pki/path_fetch_issuers.go @@ -137,10 +137,12 @@ for the OCSP servers attribute. See also RFC 5280 Section 4.2.2.1.`, fields["enable_aia_url_templating"] = &framework.FieldSchema{ Type: framework.TypeBool, Description: `Whether or not to enabling templating of the -above AIA fields. When templating is enabled the special values '{{issuer_id}}' -and '{{cluster_path}}' are available, but the addresses are not checked for -URL validity until issuance time. This requires /config/cluster's path to be -set on all PR Secondary clusters.`, +above AIA fields. When templating is enabled the special values '{{issuer_id}}', +'{{cluster_path}}', '{{cluster_aia_path}}' are available, but the addresses are +not checked for URL validity until issuance time. Using '{{cluster_path}}' +requires /config/cluster's 'path' member to be set on all PR Secondary clusters +and using '{{cluster_aia_path}}' requires /config/cluster's 'aia_path' member +to be set on all PR secondary clusters.`, Default: false, } diff --git a/builtin/logical/pki/storage.go b/builtin/logical/pki/storage.go index f8861a97c..01a46d5aa 100644 --- a/builtin/logical/pki/storage.go +++ b/builtin/logical/pki/storage.go @@ -197,7 +197,8 @@ type issuerConfigEntry struct { } type clusterConfigEntry struct { - Path string `json:"path"` + Path string `json:"path"` + AIAPath string `json:"aia_path"` } type aiaConfigEntry struct { @@ -232,15 +233,18 @@ func (c *aiaConfigEntry) toURLEntries(sc *storageContext, issuer issuerID) (*cer templated := make([]string, len(*source)) for index, uri := range *source { if strings.Contains(uri, "{{cluster_path}}") && len(cfg.Path) == 0 { - return nil, fmt.Errorf("unable to template AIA URLs as we lack local cluster address information") + return nil, fmt.Errorf("unable to template AIA URLs as we lack local cluster address information (path)") + } + if strings.Contains(uri, "{{cluster_aia_path}}") && len(cfg.AIAPath) == 0 { + return nil, fmt.Errorf("unable to template AIA URLs as we lack local cluster address information (aia_path)") } - if strings.Contains(uri, "{{issuer_id}}") && len(issuer) == 0 { // Elide issuer AIA info as we lack an issuer_id. return nil, fmt.Errorf("unable to template AIA URLs as we lack an issuer_id for this operation") } uri = strings.ReplaceAll(uri, "{{cluster_path}}", cfg.Path) + uri = strings.ReplaceAll(uri, "{{cluster_aia_path}}", cfg.AIAPath) uri = strings.ReplaceAll(uri, "{{issuer_id}}", issuer.String()) templated[index] = uri } diff --git a/website/content/api-docs/secret/pki.mdx b/website/content/api-docs/secret/pki.mdx index a79dadc63..55ca0dc66 100644 --- a/website/content/api-docs/secret/pki.mdx +++ b/website/content/api-docs/secret/pki.mdx @@ -2164,9 +2164,11 @@ do so, import a new issuer and a new `issuer_id` will be assigned. - `enable_aia_url_templating` `(bool: false)` - Specifies that the above AIA URL values (`issuing_certificates`, `crl_distribution_points`, and `ocsp_servers`) should be templated. This replaces the literal value - `{{issuer_id}}` with the ID of the issuer doing the issuance, and the + `{{issuer_id}}` with the ID of the issuer doing the issuance, the literal value `{{cluster_path}}` with the value of `path` from the - cluster-local configuration endpoint `/config/cluster`. + cluster-local configuration endpoint `/config/cluster`, and the + literal value '{{cluster_aia_path}}' with the value of `aia_path` from + the cluster-local configuration endpoint `/config/cluster`. ~> **Note**: If no cluster-local address is present and templating is used, issuance will fail. @@ -2941,18 +2943,22 @@ parameter. [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. -- `enable_templating` `(bool: false)` - Specifies that the above AIA +- `enable_aia_url_templating` `(bool: false)` - Specifies that the above AIA URL values (`issuing_certificates`, `crl_distribution_points`, and `ocsp_servers`) should be templated. This replaces the literal value - `{{issuer_id}}` with the ID of the issuer doing the issuance, and the + `{{issuer_id}}` with the ID of the issuer doing the issuance, the literal value `{{cluster_path}}` with the value of `path` from the - cluster-local configuration endpoint `/config/cluster`. + cluster-local configuration endpoint `/config/cluster`, and the + literal value '{{cluster_aia_path}}' with the value of `aia_path` from + the cluster-local configuration endpoint `/config/cluster`. For example, the following values can be used globally to ensure all AIA - URIs use the cluster-local, per-issuer canonical reference: + URIs use the cluster-local, per-issuer canonical reference, but with + the issuing CA certificate and CRL distribution points to potentially + use an external, non-Vault CDN. - - `issuing_certificates={{cluster_path}}/issuer/{{issuer_id}}/der` - - `crl_distribution_points={{cluster_path}}/issuer/{{issuer_id}}/crl/der` + - `issuing_certificates={{cluster_aia_path}}/issuer/{{issuer_id}}/der` + - `crl_distribution_points={{cluster_aia_path}}/issuer/{{issuer_id}}/crl/der` - `ocsp_servers={{cluster_path}}/ocsp` ~> **Note**: If no cluster-local address is present and templating is used, @@ -3138,10 +3144,15 @@ $ curl \ This endpoint fetches the cluster-local configuration. -Presently the only cluster-local config option is `path`, which sets the URL -to this mount on a particular performance replication cluster. This is useful -for populating `{{cluster_path}}` for AIA URL templating, but may be used for -other uses in the future. +The cluster-local config has `path`, which sets the URL to this mount on +a particular performance replication cluster. This is useful for populating +`{{cluster_path}}` during AIA URL templating, but may be used for other +values in the future. + +It also has `aia_path`, which allows using a non-Vault, external responder, +setting the `{{cluster_aia_path}}` value for AIA URL templating. This is +useful for distributing CA and CRL information over an unsecured, non-TLS +channel. | Method | Path | | :----- | :-------------------- | @@ -3163,7 +3174,8 @@ $ curl \ "renewable": false, "lease_duration": 0, "data": { - "path": "" + "path": "", + "aia_path": "" }, "auth": null } @@ -3173,9 +3185,15 @@ $ curl \ This endpoint sets cluster-local configuration. -Presently the only cluster-local config option is `path`, which sets the URL -to this mount on a particular performance replication cluster. This is useful -for AIA URL templating. +The cluster-local config has `path`, which sets the URL to this mount on +a particular performance replication cluster. This is useful for populating +`{{cluster_path}}` during AIA URL templating, but may be used for other +values in the future. + +It also has `aia_path`, which allows using a non-Vault, external responder, +setting the `{{cluster_aia_path}}` value for AIA URL templating. This is +useful for distributing CA and CRL information over an unsecured, non-TLS +channel. | Method | Path | | :----- | :-------------------- | @@ -3187,11 +3205,18 @@ for AIA URL templating. cluster's API mount path, including any namespaces as path components. For example, `https://pr-a.vault.example.com/v1/ns1/pki-root`. +- `aia_path` `(string: "")` - Specifies the path to this performance replication + cluster's AIA distribution point; may refer to an external, non-Vault responder. + This is for resolving AIA URLs and providing the `{{cluster_aia_path}}` template + parameter and will not be used for other purposes. As such, unlike `path` above, + this could safely be an insecure transit mechanism (like HTTP without TLS). + #### Sample Payload ```json { - "path": ["https://..."] + "path": "https://...", + "aia_path": "http://..." } ```