Add explicit cn_validations field to PKI Roles (#15996)
* Add cn_validations PKI Role parameter This new parameter allows disabling all validations on a common name, enabled by default on sign-verbatim and issuer generation options. Presently, the default behavior is to allow either an email address (denoted with an @ in the name) or a hostname to pass validation. Operators can restrict roles to just a single option (e.g., for email certs, limit CNs to have strictly email addresses and not hostnames). By setting the value to `disabled`, CNs of other formats can be accepted without validating their contents against our minimal correctness checks for email/hostname/wildcard that we typically apply even when broad permissions (allow_any_name=true, enforce_hostnames=false, and allow_wildcard_certificates=true) are granted on the role. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Update PKI tests for cn_validation support Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add PKI API documentation on cn_validations Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add changelog Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
parent
3496bc0416
commit
4e6a9741ee
|
@ -3519,6 +3519,7 @@ func TestReadWriteDeleteRoles(t *testing.T) {
|
||||||
"street_address": []interface{}{},
|
"street_address": []interface{}{},
|
||||||
"code_signing_flag": false,
|
"code_signing_flag": false,
|
||||||
"issuer_ref": "default",
|
"issuer_ref": "default",
|
||||||
|
"cn_validations": []interface{}{"email", "hostname"},
|
||||||
}
|
}
|
||||||
|
|
||||||
if diff := deep.Equal(expectedData, resp.Data); len(diff) > 0 {
|
if diff := deep.Equal(expectedData, resp.Data); len(diff) > 0 {
|
||||||
|
@ -4123,6 +4124,7 @@ type IssuanceRegression struct {
|
||||||
AllowSubdomains MultiBool
|
AllowSubdomains MultiBool
|
||||||
AllowLocalhost MultiBool
|
AllowLocalhost MultiBool
|
||||||
AllowWildcardCertificates MultiBool
|
AllowWildcardCertificates MultiBool
|
||||||
|
CNValidations []string
|
||||||
CommonName string
|
CommonName string
|
||||||
Issued bool
|
Issued bool
|
||||||
}
|
}
|
||||||
|
@ -4142,11 +4144,15 @@ func RoleIssuanceRegressionHelper(t *testing.T, b *backend, s logical.Storage, i
|
||||||
"allow_subdomains": AllowSubdomains,
|
"allow_subdomains": AllowSubdomains,
|
||||||
"allow_localhost": AllowLocalhost,
|
"allow_localhost": AllowLocalhost,
|
||||||
"allow_wildcard_certificates": AllowWildcardCertificates,
|
"allow_wildcard_certificates": AllowWildcardCertificates,
|
||||||
|
"cn_validations": test.CNValidations,
|
||||||
// TODO: test across this vector as well. Currently certain wildcard
|
// TODO: test across this vector as well. Currently certain wildcard
|
||||||
// matching is broken with it enabled (such as x*x.foo).
|
// matching is broken with it enabled (such as x*x.foo).
|
||||||
"enforce_hostnames": false,
|
"enforce_hostnames": false,
|
||||||
"key_type": "ec",
|
"key_type": "ec",
|
||||||
"key_bits": 256,
|
"key_bits": 256,
|
||||||
|
"no_store": true,
|
||||||
|
// With the CN Validations field, ensure we prevent CN from appearing
|
||||||
|
// in SANs.
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -4154,6 +4160,7 @@ func RoleIssuanceRegressionHelper(t *testing.T, b *backend, s logical.Storage, i
|
||||||
|
|
||||||
resp, err = CBWrite(b, s, "issue/"+role, map[string]interface{}{
|
resp, err = CBWrite(b, s, "issue/"+role, map[string]interface{}{
|
||||||
"common_name": test.CommonName,
|
"common_name": test.CommonName,
|
||||||
|
"exclude_cn_from_sans": true,
|
||||||
})
|
})
|
||||||
|
|
||||||
haveErr := err != nil || resp == nil
|
haveErr := err != nil || resp == nil
|
||||||
|
@ -4182,149 +4189,166 @@ func TestBackend_Roles_IssuanceRegression(t *testing.T) {
|
||||||
// Allowed contains globs, but globbing not allowed, resulting in all
|
// Allowed contains globs, but globbing not allowed, resulting in all
|
||||||
// issuances failing. Note that tests against issuing a wildcard with
|
// issuances failing. Note that tests against issuing a wildcard with
|
||||||
// a bare domain will be covered later.
|
// a bare domain will be covered later.
|
||||||
/* 0 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, "baz.fud.bar.foo", false},
|
/* 0 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "baz.fud.bar.foo", false},
|
||||||
/* 1 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, "*.fud.bar.foo", false},
|
/* 1 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.fud.bar.foo", false},
|
||||||
/* 2 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, "fud.bar.foo", false},
|
/* 2 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "fud.bar.foo", false},
|
||||||
/* 3 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, "*.bar.foo", false},
|
/* 3 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.bar.foo", false},
|
||||||
/* 4 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, "bar.foo", false},
|
/* 4 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "bar.foo", false},
|
||||||
/* 5 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, "*.foo", false},
|
/* 5 */ {[]string{"*.*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.foo", false},
|
||||||
/* 6 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, "foo", false},
|
/* 6 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "foo", false},
|
||||||
/* 7 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, "baz.fud.bar.foo", false},
|
/* 7 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "baz.fud.bar.foo", false},
|
||||||
/* 8 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, "*.fud.bar.foo", false},
|
/* 8 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.fud.bar.foo", false},
|
||||||
/* 9 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, "fud.bar.foo", false},
|
/* 9 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "fud.bar.foo", false},
|
||||||
/* 10 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, "*.bar.foo", false},
|
/* 10 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "*.bar.foo", false},
|
||||||
/* 11 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, "bar.foo", false},
|
/* 11 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "bar.foo", false},
|
||||||
/* 12 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, "foo", false},
|
/* 12 */ {[]string{"*.foo"}, MAny, MFalse, MAny, MAny, MAny, nil, "foo", false},
|
||||||
|
|
||||||
// === Localhost sanity === //
|
// === Localhost sanity === //
|
||||||
// Localhost forbidden, not matching allowed domains -> not issued
|
// Localhost forbidden, not matching allowed domains -> not issued
|
||||||
/* 13 */ {[]string{"*.*.foo"}, MAny, MAny, MAny, MFalse, MAny, "localhost", false},
|
/* 13 */ {[]string{"*.*.foo"}, MAny, MAny, MAny, MFalse, MAny, nil, "localhost", false},
|
||||||
// Localhost allowed, not matching allowed domains -> issued
|
// Localhost allowed, not matching allowed domains -> issued
|
||||||
/* 14 */ {[]string{"*.*.foo"}, MAny, MAny, MAny, MTrue, MAny, "localhost", true},
|
/* 14 */ {[]string{"*.*.foo"}, MAny, MAny, MAny, MTrue, MAny, nil, "localhost", true},
|
||||||
// Localhost allowed via allowed domains (and bare allowed), not by AllowLocalhost -> issued
|
// Localhost allowed via allowed domains (and bare allowed), not by AllowLocalhost -> issued
|
||||||
/* 15 */ {[]string{"localhost"}, MTrue, MAny, MAny, MFalse, MAny, "localhost", true},
|
/* 15 */ {[]string{"localhost"}, MTrue, MAny, MAny, MFalse, MAny, nil, "localhost", true},
|
||||||
// Localhost allowed via allowed domains (and bare not allowed), not by AllowLocalhost -> not issued
|
// Localhost allowed via allowed domains (and bare not allowed), not by AllowLocalhost -> not issued
|
||||||
/* 16 */ {[]string{"localhost"}, MFalse, MAny, MAny, MFalse, MAny, "localhost", false},
|
/* 16 */ {[]string{"localhost"}, MFalse, MAny, MAny, MFalse, MAny, nil, "localhost", false},
|
||||||
// Localhost allowed via allowed domains (but bare not allowed), and by AllowLocalhost -> issued
|
// Localhost allowed via allowed domains (but bare not allowed), and by AllowLocalhost -> issued
|
||||||
/* 17 */ {[]string{"localhost"}, MFalse, MAny, MAny, MTrue, MAny, "localhost", true},
|
/* 17 */ {[]string{"localhost"}, MFalse, MAny, MAny, MTrue, MAny, nil, "localhost", true},
|
||||||
|
|
||||||
// === Bare wildcard issuance == //
|
// === Bare wildcard issuance == //
|
||||||
// allowed_domains contains one or more wildcards and bare domains allowed,
|
// allowed_domains contains one or more wildcards and bare domains allowed,
|
||||||
// resulting in the cert being issued.
|
// resulting in the cert being issued.
|
||||||
/* 18 */ {[]string{"*.foo"}, MTrue, MAny, MAny, MAny, MTrue, "*.foo", true},
|
/* 18 */ {[]string{"*.foo"}, MTrue, MAny, MAny, MAny, MTrue, nil, "*.foo", true},
|
||||||
/* 19 */ {[]string{"*.*.foo"}, MTrue, MAny, MAny, MAny, MAny, "*.*.foo", false}, // Does not conform to RFC 6125
|
/* 19 */ {[]string{"*.*.foo"}, MTrue, MAny, MAny, MAny, MAny, nil, "*.*.foo", false}, // Does not conform to RFC 6125
|
||||||
|
|
||||||
// === Double Leading Glob Testing === //
|
// === Double Leading Glob Testing === //
|
||||||
// Allowed contains globs, but glob allowed so certain matches work.
|
// Allowed contains globs, but glob allowed so certain matches work.
|
||||||
// The value of bare and localhost does not impact these results.
|
// The value of bare and localhost does not impact these results.
|
||||||
/* 20 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "baz.fud.bar.foo", true}, // glob domains allow infinite subdomains
|
/* 20 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "baz.fud.bar.foo", true}, // glob domains allow infinite subdomains
|
||||||
/* 21 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, "*.fud.bar.foo", true}, // glob domain allows wildcard of subdomains
|
/* 21 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, nil, "*.fud.bar.foo", true}, // glob domain allows wildcard of subdomains
|
||||||
/* 22 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "fud.bar.foo", true},
|
/* 22 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "fud.bar.foo", true},
|
||||||
/* 23 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, "*.bar.foo", true}, // Regression fix: Vault#13530
|
/* 23 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, nil, "*.bar.foo", true}, // Regression fix: Vault#13530
|
||||||
/* 24 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "bar.foo", false},
|
/* 24 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "bar.foo", false},
|
||||||
/* 25 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "*.foo", false},
|
/* 25 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "*.foo", false},
|
||||||
/* 26 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "foo", false},
|
/* 26 */ {[]string{"*.*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "foo", false},
|
||||||
|
|
||||||
// Allowed contains globs, but glob and subdomain both work, so we expect
|
// Allowed contains globs, but glob and subdomain both work, so we expect
|
||||||
// wildcard issuance to work as well. The value of bare and localhost does
|
// wildcard issuance to work as well. The value of bare and localhost does
|
||||||
// not impact these results.
|
// not impact these results.
|
||||||
/* 27 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "baz.fud.bar.foo", true},
|
/* 27 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "baz.fud.bar.foo", true},
|
||||||
/* 28 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, "*.fud.bar.foo", true},
|
/* 28 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, nil, "*.fud.bar.foo", true},
|
||||||
/* 29 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "fud.bar.foo", true},
|
/* 29 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "fud.bar.foo", true},
|
||||||
/* 30 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, "*.bar.foo", true}, // Regression fix: Vault#13530
|
/* 30 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, nil, "*.bar.foo", true}, // Regression fix: Vault#13530
|
||||||
/* 31 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "bar.foo", false},
|
/* 31 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "bar.foo", false},
|
||||||
/* 32 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "*.foo", false},
|
/* 32 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "*.foo", false},
|
||||||
/* 33 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "foo", false},
|
/* 33 */ {[]string{"*.*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "foo", false},
|
||||||
|
|
||||||
// === Single Leading Glob Testing === //
|
// === Single Leading Glob Testing === //
|
||||||
// Allowed contains globs, but glob allowed so certain matches work.
|
// Allowed contains globs, but glob allowed so certain matches work.
|
||||||
// The value of bare and localhost does not impact these results.
|
// The value of bare and localhost does not impact these results.
|
||||||
/* 34 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "baz.fud.bar.foo", true}, // glob domains allow infinite subdomains
|
/* 34 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "baz.fud.bar.foo", true}, // glob domains allow infinite subdomains
|
||||||
/* 35 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, "*.fud.bar.foo", true}, // glob domain allows wildcard of subdomains
|
/* 35 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, nil, "*.fud.bar.foo", true}, // glob domain allows wildcard of subdomains
|
||||||
/* 36 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "fud.bar.foo", true}, // glob domains allow infinite subdomains
|
/* 36 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "fud.bar.foo", true}, // glob domains allow infinite subdomains
|
||||||
/* 37 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, "*.bar.foo", true}, // glob domain allows wildcards of subdomains
|
/* 37 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MTrue, nil, "*.bar.foo", true}, // glob domain allows wildcards of subdomains
|
||||||
/* 38 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "bar.foo", true},
|
/* 38 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "bar.foo", true},
|
||||||
/* 39 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, "foo", false},
|
/* 39 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MAny, nil, "foo", false},
|
||||||
|
|
||||||
// Allowed contains globs, but glob and subdomain both work, so we expect
|
// Allowed contains globs, but glob and subdomain both work, so we expect
|
||||||
// wildcard issuance to work as well. The value of bare and localhost does
|
// wildcard issuance to work as well. The value of bare and localhost does
|
||||||
// not impact these results.
|
// not impact these results.
|
||||||
/* 40 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "baz.fud.bar.foo", true},
|
/* 40 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "baz.fud.bar.foo", true},
|
||||||
/* 41 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, "*.fud.bar.foo", true},
|
/* 41 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, nil, "*.fud.bar.foo", true},
|
||||||
/* 42 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "fud.bar.foo", true},
|
/* 42 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "fud.bar.foo", true},
|
||||||
/* 43 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, "*.bar.foo", true},
|
/* 43 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MTrue, nil, "*.bar.foo", true},
|
||||||
/* 44 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "bar.foo", true},
|
/* 44 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "bar.foo", true},
|
||||||
/* 45 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, "foo", false},
|
/* 45 */ {[]string{"*.foo"}, MAny, MTrue, MTrue, MAny, MAny, nil, "foo", false},
|
||||||
|
|
||||||
// === Only base domain name === //
|
// === Only base domain name === //
|
||||||
// Allowed contains only domain components, but subdomains not allowed. This
|
// Allowed contains only domain components, but subdomains not allowed. This
|
||||||
// results in most issuances failing unless we allow bare domains, in which
|
// results in most issuances failing unless we allow bare domains, in which
|
||||||
// case only the final issuance for "foo" will succeed.
|
// case only the final issuance for "foo" will succeed.
|
||||||
/* 46 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, "baz.fud.bar.foo", false},
|
/* 46 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "baz.fud.bar.foo", false},
|
||||||
/* 47 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, "*.fud.bar.foo", false},
|
/* 47 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "*.fud.bar.foo", false},
|
||||||
/* 48 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, "fud.bar.foo", false},
|
/* 48 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "fud.bar.foo", false},
|
||||||
/* 49 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, "*.bar.foo", false},
|
/* 49 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "*.bar.foo", false},
|
||||||
/* 50 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, "bar.foo", false},
|
/* 50 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "bar.foo", false},
|
||||||
/* 51 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, "*.foo", false},
|
/* 51 */ {[]string{"foo"}, MAny, MAny, MFalse, MAny, MAny, nil, "*.foo", false},
|
||||||
/* 52 */ {[]string{"foo"}, MFalse, MAny, MFalse, MAny, MAny, "foo", false},
|
/* 52 */ {[]string{"foo"}, MFalse, MAny, MFalse, MAny, MAny, nil, "foo", false},
|
||||||
/* 53 */ {[]string{"foo"}, MTrue, MAny, MFalse, MAny, MAny, "foo", true},
|
/* 53 */ {[]string{"foo"}, MTrue, MAny, MFalse, MAny, MAny, nil, "foo", true},
|
||||||
|
|
||||||
// Allowed contains only domain components, and subdomains are now allowed.
|
// Allowed contains only domain components, and subdomains are now allowed.
|
||||||
// This results in most issuances succeeding, with the exception of the
|
// This results in most issuances succeeding, with the exception of the
|
||||||
// base foo, which is still governed by base's value.
|
// base foo, which is still governed by base's value.
|
||||||
/* 54 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, "baz.fud.bar.foo", true},
|
/* 54 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, nil, "baz.fud.bar.foo", true},
|
||||||
/* 55 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, "*.fud.bar.foo", true},
|
/* 55 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "*.fud.bar.foo", true},
|
||||||
/* 56 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, "fud.bar.foo", true},
|
/* 56 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, nil, "fud.bar.foo", true},
|
||||||
/* 57 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, "*.bar.foo", true},
|
/* 57 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "*.bar.foo", true},
|
||||||
/* 58 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, "bar.foo", true},
|
/* 58 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MAny, nil, "bar.foo", true},
|
||||||
/* 59 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, "*.foo", true},
|
/* 59 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "*.foo", true},
|
||||||
/* 60 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, "x*x.foo", true}, // internal wildcards should be allowed per RFC 6125/6.4.3
|
/* 60 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "x*x.foo", true}, // internal wildcards should be allowed per RFC 6125/6.4.3
|
||||||
/* 61 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, "*x.foo", true}, // prefix wildcards should be allowed per RFC 6125/6.4.3
|
/* 61 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "*x.foo", true}, // prefix wildcards should be allowed per RFC 6125/6.4.3
|
||||||
/* 62 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, "x*.foo", true}, // suffix wildcards should be allowed per RFC 6125/6.4.3
|
/* 62 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MTrue, nil, "x*.foo", true}, // suffix wildcards should be allowed per RFC 6125/6.4.3
|
||||||
/* 63 */ {[]string{"foo"}, MFalse, MAny, MTrue, MAny, MAny, "foo", false},
|
/* 63 */ {[]string{"foo"}, MFalse, MAny, MTrue, MAny, MAny, nil, "foo", false},
|
||||||
/* 64 */ {[]string{"foo"}, MTrue, MAny, MTrue, MAny, MAny, "foo", true},
|
/* 64 */ {[]string{"foo"}, MTrue, MAny, MTrue, MAny, MAny, nil, "foo", true},
|
||||||
|
|
||||||
// === Internal Glob Matching === //
|
// === Internal Glob Matching === //
|
||||||
// Basic glob matching requirements
|
// Basic glob matching requirements
|
||||||
/* 65 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, "xerox.foo", true},
|
/* 65 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xerox.foo", true},
|
||||||
/* 66 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, "xylophone.files.pyrex.foo", true}, // globs can match across subdomains
|
/* 66 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xylophone.files.pyrex.foo", true}, // globs can match across subdomains
|
||||||
/* 67 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, "xercex.bar.foo", false}, // x.foo isn't matched
|
/* 67 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xercex.bar.foo", false}, // x.foo isn't matched
|
||||||
/* 68 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, "bar.foo", false}, // x*x isn't matched.
|
/* 68 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "bar.foo", false}, // x*x isn't matched.
|
||||||
/* 69 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, "*.foo", false}, // unrelated wildcard
|
/* 69 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.foo", false}, // unrelated wildcard
|
||||||
/* 70 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, "*.x*x.foo", false}, // Does not conform to RFC 6125
|
/* 70 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.x*x.foo", false}, // Does not conform to RFC 6125
|
||||||
/* 71 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, "*.xyx.foo", false}, // Globs and Subdomains do not layer per docs.
|
/* 71 */ {[]string{"x*x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.xyx.foo", false}, // Globs and Subdomains do not layer per docs.
|
||||||
|
|
||||||
// Various requirements around x*x.foo wildcard matching.
|
// Various requirements around x*x.foo wildcard matching.
|
||||||
/* 72 */ {[]string{"x*x.foo"}, MFalse, MFalse, MAny, MAny, MAny, "x*x.foo", false}, // base disabled, shouldn't match wildcard
|
/* 72 */ {[]string{"x*x.foo"}, MFalse, MFalse, MAny, MAny, MAny, nil, "x*x.foo", false}, // base disabled, shouldn't match wildcard
|
||||||
/* 73 */ {[]string{"x*x.foo"}, MFalse, MTrue, MAny, MAny, MTrue, "x*x.foo", true}, // base disallowed, but globbing allowed and should match
|
/* 73 */ {[]string{"x*x.foo"}, MFalse, MTrue, MAny, MAny, MTrue, nil, "x*x.foo", true}, // base disallowed, but globbing allowed and should match
|
||||||
/* 74 */ {[]string{"x*x.foo"}, MTrue, MAny, MAny, MAny, MTrue, "x*x.foo", true}, // base allowed, should match wildcard
|
/* 74 */ {[]string{"x*x.foo"}, MTrue, MAny, MAny, MAny, MTrue, nil, "x*x.foo", true}, // base allowed, should match wildcard
|
||||||
|
|
||||||
// Basic glob matching requirements with internal dots.
|
// Basic glob matching requirements with internal dots.
|
||||||
/* 75 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "xerox.foo", false}, // missing dots
|
/* 75 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xerox.foo", false}, // missing dots
|
||||||
/* 76 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "x.ero.x.foo", true},
|
/* 76 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "x.ero.x.foo", true},
|
||||||
/* 77 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "xylophone.files.pyrex.foo", false}, // missing dots
|
/* 77 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xylophone.files.pyrex.foo", false}, // missing dots
|
||||||
/* 78 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "x.ylophone.files.pyre.x.foo", true}, // globs can match across subdomains
|
/* 78 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "x.ylophone.files.pyre.x.foo", true}, // globs can match across subdomains
|
||||||
/* 79 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "xercex.bar.foo", false}, // x.foo isn't matched
|
/* 79 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "xercex.bar.foo", false}, // x.foo isn't matched
|
||||||
/* 80 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "bar.foo", false}, // x.*.x isn't matched.
|
/* 80 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "bar.foo", false}, // x.*.x isn't matched.
|
||||||
/* 81 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "*.foo", false}, // unrelated wildcard
|
/* 81 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.foo", false}, // unrelated wildcard
|
||||||
/* 82 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "*.x.*.x.foo", false}, // Does not conform to RFC 6125
|
/* 82 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.x.*.x.foo", false}, // Does not conform to RFC 6125
|
||||||
/* 83 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, "*.x.y.x.foo", false}, // Globs and Subdomains do not layer per docs.
|
/* 83 */ {[]string{"x.*.x.foo"}, MAny, MTrue, MAny, MAny, MAny, nil, "*.x.y.x.foo", false}, // Globs and Subdomains do not layer per docs.
|
||||||
|
|
||||||
// === Wildcard restriction testing === //
|
// === Wildcard restriction testing === //
|
||||||
/* 84 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MFalse, "*.fud.bar.foo", false}, // glob domain allows wildcard of subdomains
|
/* 84 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MFalse, nil, "*.fud.bar.foo", false}, // glob domain allows wildcard of subdomains
|
||||||
/* 85 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MFalse, "*.bar.foo", false}, // glob domain allows wildcards of subdomains
|
/* 85 */ {[]string{"*.foo"}, MAny, MTrue, MFalse, MAny, MFalse, nil, "*.bar.foo", false}, // glob domain allows wildcards of subdomains
|
||||||
/* 86 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, "*.fud.bar.foo", false},
|
/* 86 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "*.fud.bar.foo", false},
|
||||||
/* 87 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, "*.bar.foo", false},
|
/* 87 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "*.bar.foo", false},
|
||||||
/* 88 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, "*.foo", false},
|
/* 88 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "*.foo", false},
|
||||||
/* 89 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, "x*x.foo", false},
|
/* 89 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "x*x.foo", false},
|
||||||
/* 90 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, "*x.foo", false},
|
/* 90 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "*x.foo", false},
|
||||||
/* 91 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, "x*.foo", false},
|
/* 91 */ {[]string{"foo"}, MAny, MAny, MTrue, MAny, MFalse, nil, "x*.foo", false},
|
||||||
/* 92 */ {[]string{"x*x.foo"}, MTrue, MAny, MAny, MAny, MFalse, "x*x.foo", false},
|
/* 92 */ {[]string{"x*x.foo"}, MTrue, MAny, MAny, MAny, MFalse, nil, "x*x.foo", false},
|
||||||
/* 93 */ {[]string{"*.foo"}, MFalse, MFalse, MAny, MAny, MAny, "*.foo", false}, // Bare and globs forbidden despite (potentially) allowing wildcards.
|
/* 93 */ {[]string{"*.foo"}, MFalse, MFalse, MAny, MAny, MAny, nil, "*.foo", false}, // Bare and globs forbidden despite (potentially) allowing wildcards.
|
||||||
/* 94 */ {[]string{"x.*.x.foo"}, MAny, MAny, MAny, MAny, MAny, "x.*.x.foo", false}, // Does not conform to RFC 6125
|
/* 94 */ {[]string{"x.*.x.foo"}, MAny, MAny, MAny, MAny, MAny, nil, "x.*.x.foo", false}, // Does not conform to RFC 6125
|
||||||
|
|
||||||
|
// === CN validation allowances === //
|
||||||
|
/* 95 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "*.fud.bar.foo", true},
|
||||||
|
/* 96 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "*.fud.*.foo", true},
|
||||||
|
/* 97 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "*.bar.*.bar", true},
|
||||||
|
/* 98 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "foo@foo", true},
|
||||||
|
/* 99 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "foo@foo@foo", true},
|
||||||
|
/* 100 */ {[]string{"foo"}, MAny, MAny, MAny, MAny, MAny, []string{"disabled"}, "bar@bar@bar", true},
|
||||||
|
/* 101 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar@bar@bar", false},
|
||||||
|
/* 102 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar@bar", false},
|
||||||
|
/* 103 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar@foo", true},
|
||||||
|
/* 104 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"hostname"}, "bar@foo", false},
|
||||||
|
/* 105 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"hostname"}, "bar@bar", false},
|
||||||
|
/* 106 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"hostname"}, "bar.foo", true},
|
||||||
|
/* 107 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"hostname"}, "bar.bar", false},
|
||||||
|
/* 108 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar.foo", false},
|
||||||
|
/* 109 */ {[]string{"foo"}, MTrue, MTrue, MTrue, MTrue, MTrue, []string{"email"}, "bar.bar", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(testCases) != 95 {
|
if len(testCases) != 110 {
|
||||||
t.Fatalf("misnumbered test case entries will make it hard to find bugs: %v", len(testCases))
|
t.Fatalf("misnumbered test case entries will make it hard to find bugs: %v", len(testCases))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ func (b *backend) getGenerationParams(ctx context.Context, storage logical.Stora
|
||||||
StreetAddress: data.Get("street_address").([]string),
|
StreetAddress: data.Get("street_address").([]string),
|
||||||
PostalCode: data.Get("postal_code").([]string),
|
PostalCode: data.Get("postal_code").([]string),
|
||||||
NotBeforeDuration: time.Duration(data.Get("not_before_duration").(int)) * time.Second,
|
NotBeforeDuration: time.Duration(data.Get("not_before_duration").(int)) * time.Second,
|
||||||
|
CNValidations: []string{"disabled"},
|
||||||
}
|
}
|
||||||
*role.AllowWildcardCertificates = true
|
*role.AllowWildcardCertificates = true
|
||||||
|
|
||||||
|
|
|
@ -243,6 +243,54 @@ func validateURISAN(b *backend, data *inputBundle, uri string) bool {
|
||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validates a given common name, ensuring its either a email or a hostname
|
||||||
|
// after validating it according to the role parameters, or disables
|
||||||
|
// validation altogether.
|
||||||
|
func validateCommonName(b *backend, data *inputBundle, name string) string {
|
||||||
|
isDisabled := len(data.role.CNValidations) == 1 && data.role.CNValidations[0] == "disabled"
|
||||||
|
if isDisabled {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if validateNames(b, data, []string{name}) != "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validations weren't disabled, but the role lacked CN Validations, so
|
||||||
|
// don't restrict types. This case is hit in certain existing tests.
|
||||||
|
if len(data.role.CNValidations) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's an at in the data, ensure email type validation is allowed.
|
||||||
|
// Otherwise, ensure hostname is allowed.
|
||||||
|
if strings.Contains(name, "@") {
|
||||||
|
var allowsEmails bool
|
||||||
|
for _, validation := range data.role.CNValidations {
|
||||||
|
if validation == "email" {
|
||||||
|
allowsEmails = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allowsEmails {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var allowsHostnames bool
|
||||||
|
for _, validation := range data.role.CNValidations {
|
||||||
|
if validation == "hostname" {
|
||||||
|
allowsHostnames = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allowsHostnames {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Given a set of requested names for a certificate, verifies that all of them
|
// Given a set of requested names for a certificate, verifies that all of them
|
||||||
// match the various toggles set in the role for controlling issuance.
|
// match the various toggles set in the role for controlling issuance.
|
||||||
// If one does not pass, it is returned in the string argument.
|
// If one does not pass, it is returned in the string argument.
|
||||||
|
@ -1049,7 +1097,7 @@ func generateCreationBundle(b *backend, data *inputBundle, caSign *certutil.CAIn
|
||||||
// Check the CN. This ensures that the CN is checked even if it's
|
// Check the CN. This ensures that the CN is checked even if it's
|
||||||
// excluded from SANs.
|
// excluded from SANs.
|
||||||
if cn != "" {
|
if cn != "" {
|
||||||
badName := validateNames(b, data, []string{cn})
|
badName := validateCommonName(b, data, cn)
|
||||||
if len(badName) != 0 {
|
if len(badName) != 0 {
|
||||||
return nil, errutil.UserError{Err: fmt.Sprintf(
|
return nil, errutil.UserError{Err: fmt.Sprintf(
|
||||||
"common name %s not allowed by this role", badName)}
|
"common name %s not allowed by this role", badName)}
|
||||||
|
|
|
@ -193,6 +193,7 @@ func (b *backend) pathSignVerbatim(ctx context.Context, req *logical.Request, da
|
||||||
AllowedOtherSANs: []string{"*"},
|
AllowedOtherSANs: []string{"*"},
|
||||||
AllowedSerialNumbers: []string{"*"},
|
AllowedSerialNumbers: []string{"*"},
|
||||||
AllowedURISANs: []string{"*"},
|
AllowedURISANs: []string{"*"},
|
||||||
|
CNValidations: []string{"disabled"},
|
||||||
GenerateLease: new(bool),
|
GenerateLease: new(bool),
|
||||||
KeyUsage: data.Get("key_usage").([]string),
|
KeyUsage: data.Get("key_usage").([]string),
|
||||||
ExtKeyUsage: data.Get("ext_key_usage").([]string),
|
ExtKeyUsage: data.Get("ext_key_usage").([]string),
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/hashicorp/vault/sdk/framework"
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
"github.com/hashicorp/vault/sdk/helper/certutil"
|
"github.com/hashicorp/vault/sdk/helper/certutil"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/errutil"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -384,6 +385,21 @@ for "generate_lease".`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"cn_validations": {
|
||||||
|
Type: framework.TypeCommaStringSlice,
|
||||||
|
Default: []string{"email", "hostname"},
|
||||||
|
Description: `List of allowed validations to run against the
|
||||||
|
Common Name field. Values can include 'email' to validate the CN is a email
|
||||||
|
address, 'hostname' to validate the CN is a valid hostname (potentially
|
||||||
|
including wildcards). When multiple validations are specified, these take
|
||||||
|
OR semantics (either email OR hostname are allowed). The special value
|
||||||
|
'disabled' allows disabling all CN name validations, allowing for arbitrary
|
||||||
|
non-Hostname, non-Email address CNs.`,
|
||||||
|
DisplayAttrs: &framework.DisplayAttributes{
|
||||||
|
Name: "Common Name Validations",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"policy_identifiers": {
|
"policy_identifiers": {
|
||||||
Type: framework.TypeCommaStringSlice,
|
Type: framework.TypeCommaStringSlice,
|
||||||
Description: `A comma-separated string or list of policy OIDs, or a JSON list of qualified policy
|
Description: `A comma-separated string or list of policy OIDs, or a JSON list of qualified policy
|
||||||
|
@ -565,6 +581,18 @@ func (b *backend) getRole(ctx context.Context, s logical.Storage, n string) (*ro
|
||||||
modified = true
|
modified = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update CN Validations to be the present default, "email,hostname"
|
||||||
|
if len(result.CNValidations) == 0 {
|
||||||
|
result.CNValidations = []string{"email", "hostname"}
|
||||||
|
modified = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the role is valida fter updating.
|
||||||
|
_, err = validateRole(b, &result, ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if modified && (b.System().LocalMount() || !b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary)) {
|
if modified && (b.System().LocalMount() || !b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary)) {
|
||||||
jsonEntry, err := logical.StorageEntryJSON("role/"+n, &result)
|
jsonEntry, err := logical.StorageEntryJSON("role/"+n, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -660,6 +688,7 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
|
||||||
GenerateLease: new(bool),
|
GenerateLease: new(bool),
|
||||||
NoStore: data.Get("no_store").(bool),
|
NoStore: data.Get("no_store").(bool),
|
||||||
RequireCN: data.Get("require_cn").(bool),
|
RequireCN: data.Get("require_cn").(bool),
|
||||||
|
CNValidations: data.Get("cn_validations").([]string),
|
||||||
AllowedSerialNumbers: data.Get("allowed_serial_numbers").([]string),
|
AllowedSerialNumbers: data.Get("allowed_serial_numbers").([]string),
|
||||||
PolicyIdentifiers: getPolicyIdentifier(data, nil),
|
PolicyIdentifiers: getPolicyIdentifier(data, nil),
|
||||||
BasicConstraintsValidForNonCA: data.Get("basic_constraints_valid_for_non_ca").(bool),
|
BasicConstraintsValidForNonCA: data.Get("basic_constraints_valid_for_non_ca").(bool),
|
||||||
|
@ -781,6 +810,12 @@ func validateRole(b *backend, entry *roleEntry, ctx context.Context, s logical.S
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensures CNValidations are alright
|
||||||
|
entry.CNValidations, err = checkCNValidations(entry.CNValidations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errutil.UserError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -848,6 +883,7 @@ func (b *backend) pathRolePatch(ctx context.Context, req *logical.Request, data
|
||||||
GenerateLease: new(bool),
|
GenerateLease: new(bool),
|
||||||
NoStore: getWithExplicitDefault(data, "no_store", oldEntry.NoStore).(bool),
|
NoStore: getWithExplicitDefault(data, "no_store", oldEntry.NoStore).(bool),
|
||||||
RequireCN: getWithExplicitDefault(data, "require_cn", oldEntry.RequireCN).(bool),
|
RequireCN: getWithExplicitDefault(data, "require_cn", oldEntry.RequireCN).(bool),
|
||||||
|
CNValidations: getWithExplicitDefault(data, "cn_validations", oldEntry.CNValidations).([]string),
|
||||||
AllowedSerialNumbers: getWithExplicitDefault(data, "allowed_serial_numbers", oldEntry.AllowedSerialNumbers).([]string),
|
AllowedSerialNumbers: getWithExplicitDefault(data, "allowed_serial_numbers", oldEntry.AllowedSerialNumbers).([]string),
|
||||||
PolicyIdentifiers: getPolicyIdentifier(data, &oldEntry.PolicyIdentifiers),
|
PolicyIdentifiers: getPolicyIdentifier(data, &oldEntry.PolicyIdentifiers),
|
||||||
BasicConstraintsValidForNonCA: getWithExplicitDefault(data, "basic_constraints_valid_for_non_ca", oldEntry.BasicConstraintsValidForNonCA).(bool),
|
BasicConstraintsValidForNonCA: getWithExplicitDefault(data, "basic_constraints_valid_for_non_ca", oldEntry.BasicConstraintsValidForNonCA).(bool),
|
||||||
|
@ -1043,6 +1079,7 @@ type roleEntry struct {
|
||||||
GenerateLease *bool `json:"generate_lease,omitempty"`
|
GenerateLease *bool `json:"generate_lease,omitempty"`
|
||||||
NoStore bool `json:"no_store" mapstructure:"no_store"`
|
NoStore bool `json:"no_store" mapstructure:"no_store"`
|
||||||
RequireCN bool `json:"require_cn" mapstructure:"require_cn"`
|
RequireCN bool `json:"require_cn" mapstructure:"require_cn"`
|
||||||
|
CNValidations []string `json:"cn_validations" mapstructure:"cn_validations"`
|
||||||
AllowedOtherSANs []string `json:"allowed_other_sans" mapstructure:"allowed_other_sans"`
|
AllowedOtherSANs []string `json:"allowed_other_sans" mapstructure:"allowed_other_sans"`
|
||||||
AllowedSerialNumbers []string `json:"allowed_serial_numbers" mapstructure:"allowed_serial_numbers"`
|
AllowedSerialNumbers []string `json:"allowed_serial_numbers" mapstructure:"allowed_serial_numbers"`
|
||||||
AllowedURISANs []string `json:"allowed_uri_sans" mapstructure:"allowed_uri_sans"`
|
AllowedURISANs []string `json:"allowed_uri_sans" mapstructure:"allowed_uri_sans"`
|
||||||
|
@ -1095,6 +1132,7 @@ func (r *roleEntry) ToResponseData() map[string]interface{} {
|
||||||
"allowed_serial_numbers": r.AllowedSerialNumbers,
|
"allowed_serial_numbers": r.AllowedSerialNumbers,
|
||||||
"allowed_uri_sans": r.AllowedURISANs,
|
"allowed_uri_sans": r.AllowedURISANs,
|
||||||
"require_cn": r.RequireCN,
|
"require_cn": r.RequireCN,
|
||||||
|
"cn_validations": r.CNValidations,
|
||||||
"policy_identifiers": r.PolicyIdentifiers,
|
"policy_identifiers": r.PolicyIdentifiers,
|
||||||
"basic_constraints_valid_for_non_ca": r.BasicConstraintsValidForNonCA,
|
"basic_constraints_valid_for_non_ca": r.BasicConstraintsValidForNonCA,
|
||||||
"not_before_duration": int64(r.NotBeforeDuration.Seconds()),
|
"not_before_duration": int64(r.NotBeforeDuration.Seconds()),
|
||||||
|
@ -1110,6 +1148,52 @@ func (r *roleEntry) ToResponseData() map[string]interface{} {
|
||||||
return responseData
|
return responseData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkCNValidations(validations []string) ([]string, error) {
|
||||||
|
var haveDisabled bool
|
||||||
|
var haveEmail bool
|
||||||
|
var haveHostname bool
|
||||||
|
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
if len(validations) == 0 {
|
||||||
|
return []string{"email", "hostname"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, validation := range validations {
|
||||||
|
switch strings.ToLower(validation) {
|
||||||
|
case "disabled":
|
||||||
|
if haveDisabled {
|
||||||
|
return nil, fmt.Errorf("cn_validations value incorrect: `disabled` specified multiple times")
|
||||||
|
}
|
||||||
|
haveDisabled = true
|
||||||
|
case "email":
|
||||||
|
if haveEmail {
|
||||||
|
return nil, fmt.Errorf("cn_validations value incorrect: `email` specified multiple times")
|
||||||
|
}
|
||||||
|
haveEmail = true
|
||||||
|
case "hostname":
|
||||||
|
if haveHostname {
|
||||||
|
return nil, fmt.Errorf("cn_validations value incorrect: `hostname` specified multiple times")
|
||||||
|
}
|
||||||
|
haveHostname = true
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cn_validations value incorrect: unknown type: `%s`", validation)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, strings.ToLower(validation))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !haveDisabled && !haveEmail && !haveHostname {
|
||||||
|
return nil, fmt.Errorf("cn_validations value incorrect: must specify a value (`email` and/or `hostname`) or `disabled`")
|
||||||
|
}
|
||||||
|
|
||||||
|
if haveDisabled && (haveEmail || haveHostname) {
|
||||||
|
return nil, fmt.Errorf("cn_validations value incorrect: cannot specify `disabled` along with `email` or `hostname`")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
const pathListRolesHelpSyn = `List the existing roles in this backend`
|
const pathListRolesHelpSyn = `List the existing roles in this backend`
|
||||||
|
|
||||||
const pathListRolesHelpDesc = `Roles will be listed by the role name.`
|
const pathListRolesHelpDesc = `Roles will be listed by the role name.`
|
||||||
|
|
|
@ -291,6 +291,7 @@ func (b *backend) pathIssuerSignIntermediate(ctx context.Context, req *logical.R
|
||||||
AllowedURISANs: []string{"*"},
|
AllowedURISANs: []string{"*"},
|
||||||
NotAfter: data.Get("not_after").(string),
|
NotAfter: data.Get("not_after").(string),
|
||||||
NotBeforeDuration: time.Duration(data.Get("not_before_duration").(int)) * time.Second,
|
NotBeforeDuration: time.Duration(data.Get("not_before_duration").(int)) * time.Second,
|
||||||
|
CNValidations: []string{"disabled"},
|
||||||
}
|
}
|
||||||
*role.AllowWildcardCertificates = true
|
*role.AllowWildcardCertificates = true
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
secret/pki: Allow issuing certificates with non-domain, non-email Common Names from roles, sign-verbatim, and as issuers (`cn_validations`).
|
||||||
|
```
|
|
@ -2419,6 +2419,19 @@ request is denied.
|
||||||
`YYYY-MM-ddTHH:MM:SSZ`. Supports the Y10K end date for IEEE 802.1AR-2018
|
`YYYY-MM-ddTHH:MM:SSZ`. Supports the Y10K end date for IEEE 802.1AR-2018
|
||||||
standard devices, `9999-12-31T23:59:59Z`.
|
standard devices, `9999-12-31T23:59:59Z`.
|
||||||
|
|
||||||
|
- `cn_validations` `(list: ["email", "hostname"])` - Validations to run on the
|
||||||
|
Common Name field of the certificate. Valid values include:
|
||||||
|
|
||||||
|
- `email`, to ensure the Common Name is an email address (contains an `@` sign),
|
||||||
|
- `hostname`, to ensure the Common Name is a hostname (otherwise).
|
||||||
|
|
||||||
|
Multiple values can be separated with a comma or specified as a list and use
|
||||||
|
OR semantics (either email or hostname in the CN are allowed). When the
|
||||||
|
special value "disabled" is used (must be specified alone), none of the usual
|
||||||
|
validation is run (including but not limited to `allowed_domains` and basic
|
||||||
|
correctness validation around email addresses and domain names). This allows
|
||||||
|
non-standard CNs to be used verbatim from the request.
|
||||||
|
|
||||||
#### Sample Payload
|
#### Sample Payload
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
Loading…
Reference in New Issue