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 <alex.scheel@hashicorp.com>

* Address feedback from Max

Co-authored-by: Max Bowsher <maxbowsher@gmail.com>
Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
Co-authored-by: Max Bowsher <maxbowsher@gmail.com>
This commit is contained in:
Alexander Scheel
2023-01-10 09:51:37 -05:00
committed by GitHub
parent ab415325ab
commit 822fba38d3
6 changed files with 92 additions and 37 deletions

View File

@@ -5971,13 +5971,14 @@ func TestPKI_TemplatedAIAs(t *testing.T) {
// Setting templated AIAs should succeed. // Setting templated AIAs should succeed.
_, err := CBWrite(b, s, "config/cluster", map[string]interface{}{ _, 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) require.NoError(t, err)
aiaData := map[string]interface{}{ aiaData := map[string]interface{}{
"crl_distribution_points": "{{cluster_path}}/issuer/{{issuer_id}}/crl/der", "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", "ocsp_servers": "{{cluster_path}}/ocsp",
"enable_templating": true, "enable_templating": true,
} }
@@ -6023,7 +6024,7 @@ func TestPKI_TemplatedAIAs(t *testing.T) {
// Validate the AIA info is correctly templated. // Validate the AIA info is correctly templated.
cert := parseCert(t, resp.Data["certificate"].(string)) cert := parseCert(t, resp.Data["certificate"].(string))
require.Equal(t, cert.OCSPServer, []string{"http://localhost:8200/v1/pki/ocsp"}) 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"}) 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. // Modify our issuer to set custom AIAs: these URLs are bad.

View File

@@ -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`, 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{ Operations: map[logical.Operation]framework.OperationHandler{
@@ -51,7 +61,8 @@ func (b *backend) pathReadCluster(ctx context.Context, req *logical.Request, _ *
resp := &logical.Response{ resp := &logical.Response{
Data: map[string]interface{}{ 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 return nil, err
} }
cfg.Path = data.Get("path").(string) if value, ok := data.GetOk("path"); ok {
if !govalidator.IsURL(cfg.Path) { cfg.Path = value.(string)
return nil, fmt.Errorf("invalid, non-URL path given to cluster: %v", cfg.Path) 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 { 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{ resp := &logical.Response{
Data: map[string]interface{}{ Data: map[string]interface{}{
"path": cfg.Path, "path": cfg.Path,
"aia_path": cfg.AIAPath,
}, },
} }

View File

@@ -35,10 +35,12 @@ for the OCSP servers attribute. See also RFC 5280 Section 4.2.2.1.`,
"enable_templating": { "enable_templating": {
Type: framework.TypeBool, Type: framework.TypeBool,
Description: `Whether or not to enabling templating of the Description: `Whether or not to enabling templating of the
above AIA fields. When templating is enabled the special values '{{issuer_id}}' above AIA fields. When templating is enabled the special values '{{issuer_id}}',
and '{{cluster_path}}' are available, but the addresses are not checked for '{{cluster_path}}', and '{{cluster_aia_path}}' are available, but the addresses
URI validity until issuance time. This requires /config/cluster's path to be are not checked for URI validity until issuance time. Using '{{cluster_path}}'
set on all PR Secondary clusters.`, 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, Default: false,
}, },
}, },
@@ -59,7 +61,7 @@ set on all PR Secondary clusters.`,
func validateURLs(urls []string) string { func validateURLs(urls []string) string {
for _, curr := range urls { 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 return curr
} }
} }

View File

@@ -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{ fields["enable_aia_url_templating"] = &framework.FieldSchema{
Type: framework.TypeBool, Type: framework.TypeBool,
Description: `Whether or not to enabling templating of the Description: `Whether or not to enabling templating of the
above AIA fields. When templating is enabled the special values '{{issuer_id}}' above AIA fields. When templating is enabled the special values '{{issuer_id}}',
and '{{cluster_path}}' are available, but the addresses are not checked for '{{cluster_path}}', '{{cluster_aia_path}}' are available, but the addresses are
URL validity until issuance time. This requires /config/cluster's path to be not checked for URL validity until issuance time. Using '{{cluster_path}}'
set on all PR Secondary clusters.`, 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, Default: false,
} }

View File

@@ -197,7 +197,8 @@ type issuerConfigEntry struct {
} }
type clusterConfigEntry struct { type clusterConfigEntry struct {
Path string `json:"path"` Path string `json:"path"`
AIAPath string `json:"aia_path"`
} }
type aiaConfigEntry struct { type aiaConfigEntry struct {
@@ -232,15 +233,18 @@ func (c *aiaConfigEntry) toURLEntries(sc *storageContext, issuer issuerID) (*cer
templated := make([]string, len(*source)) templated := make([]string, len(*source))
for index, uri := range *source { for index, uri := range *source {
if strings.Contains(uri, "{{cluster_path}}") && len(cfg.Path) == 0 { 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 { if strings.Contains(uri, "{{issuer_id}}") && len(issuer) == 0 {
// Elide issuer AIA info as we lack an issuer_id. // 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") 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_path}}", cfg.Path)
uri = strings.ReplaceAll(uri, "{{cluster_aia_path}}", cfg.AIAPath)
uri = strings.ReplaceAll(uri, "{{issuer_id}}", issuer.String()) uri = strings.ReplaceAll(uri, "{{issuer_id}}", issuer.String())
templated[index] = uri templated[index] = uri
} }

View File

@@ -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 - `enable_aia_url_templating` `(bool: false)` - Specifies that the above AIA
URL values (`issuing_certificates`, `crl_distribution_points`, and URL values (`issuing_certificates`, `crl_distribution_points`, and
`ocsp_servers`) should be templated. This replaces the literal value `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 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, ~> **Note**: If no cluster-local address is present and templating is used,
issuance will fail. 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) [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. 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 URL values (`issuing_certificates`, `crl_distribution_points`, and
`ocsp_servers`) should be templated. This replaces the literal value `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 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 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` - `issuing_certificates={{cluster_aia_path}}/issuer/{{issuer_id}}/der`
- `crl_distribution_points={{cluster_path}}/issuer/{{issuer_id}}/crl/der` - `crl_distribution_points={{cluster_aia_path}}/issuer/{{issuer_id}}/crl/der`
- `ocsp_servers={{cluster_path}}/ocsp` - `ocsp_servers={{cluster_path}}/ocsp`
~> **Note**: If no cluster-local address is present and templating is used, ~> **Note**: If no cluster-local address is present and templating is used,
@@ -3138,10 +3144,15 @@ $ curl \
This endpoint fetches the cluster-local configuration. This endpoint fetches the cluster-local configuration.
Presently the only cluster-local config option is `path`, which sets the URL The cluster-local config has `path`, which sets the URL to this mount on
to this mount on a particular performance replication cluster. This is useful a particular performance replication cluster. This is useful for populating
for populating `{{cluster_path}}` for AIA URL templating, but may be used for `{{cluster_path}}` during AIA URL templating, but may be used for other
other uses in the future. 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 | | Method | Path |
| :----- | :-------------------- | | :----- | :-------------------- |
@@ -3163,7 +3174,8 @@ $ curl \
"renewable": false, "renewable": false,
"lease_duration": 0, "lease_duration": 0,
"data": { "data": {
"path": "<url>" "path": "<url>",
"aia_path": "<url>"
}, },
"auth": null "auth": null
} }
@@ -3173,9 +3185,15 @@ $ curl \
This endpoint sets cluster-local configuration. This endpoint sets cluster-local configuration.
Presently the only cluster-local config option is `path`, which sets the URL The cluster-local config has `path`, which sets the URL to this mount on
to this mount on a particular performance replication cluster. This is useful a particular performance replication cluster. This is useful for populating
for AIA URL templating. `{{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 | | Method | Path |
| :----- | :-------------------- | | :----- | :-------------------- |
@@ -3187,11 +3205,18 @@ for AIA URL templating.
cluster's API mount path, including any namespaces as path components. cluster's API mount path, including any namespaces as path components.
For example, `https://pr-a.vault.example.com/v1/ns1/pki-root`. 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 #### Sample Payload
```json ```json
{ {
"path": ["https://..."] "path": "https://...",
"aia_path": "http://..."
} }
``` ```