Change allowed_base_domain to allowed_domains and allow_base_domain to
allow_bare_domains, for comma-separated multi-domain support.
This commit is contained in:
parent
b6c49ddf01
commit
4eec9d69e8
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -11,11 +11,18 @@ DEPRECATIONS/BREAKING CHANGES:
|
|||
* As noted below in the FEATURES section, if your Vault installation contains
|
||||
a policy called `default`, new tokens created will inherit this policy
|
||||
automatically.
|
||||
* In the PKI backend, the token display name is no longer a valid option for
|
||||
providing a base domain for issuance. Since this name is prepended with the
|
||||
name of the authentication backend that issued it, it provided a faulty
|
||||
use-case at best and a confusing experience at worst. We hope to figure out
|
||||
a better per-token value in a future release.
|
||||
* In the PKI backend there have been a few minor breaking changes:
|
||||
* The token display name is no longer a valid option for providing a base
|
||||
domain for issuance. Since this name is prepended with the name of the
|
||||
authentication backend that issued it, it provided a faulty use-case at best
|
||||
and a confusing experience at worst. We hope to figure out a better
|
||||
per-token value in a future release.
|
||||
* The `allowed_base_domain` parameter has been changed to `allowed_domains`,
|
||||
which accepts a comma-separated list of domains. This allows issuing
|
||||
certificates with DNS subjects across multiple domains. If you had a
|
||||
configured `allowed_base_domain` parameter, it will be migrated
|
||||
automatically when the role is read (either via a normal read, or via
|
||||
issuing a certificate).
|
||||
|
||||
FEATURES:
|
||||
|
||||
|
|
|
@ -993,14 +993,14 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
|
|||
|
||||
// Returns a TestCheckFunc that performs various validity checks on the
|
||||
// returned certificate information, mostly within checkCertsAndPrivateKey
|
||||
getCnCheck := func(name, keyType string, key crypto.Signer, usage certUsage, validity time.Duration) logicaltest.TestCheckFunc {
|
||||
getCnCheck := func(name string, role roleEntry, key crypto.Signer, usage certUsage, validity time.Duration) logicaltest.TestCheckFunc {
|
||||
var certBundle certutil.CertBundle
|
||||
return func(resp *logical.Response) error {
|
||||
err := mapstructure.Decode(resp.Data, &certBundle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsedCertBundle, err := checkCertsAndPrivateKey(keyType, key, usage, validity, &certBundle)
|
||||
parsedCertBundle, err := checkCertsAndPrivateKey(role.KeyType, key, usage, validity, &certBundle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking generated certificate: %s", err)
|
||||
}
|
||||
|
@ -1034,7 +1034,8 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
|
|||
// Common names to test with the various role flags toggled
|
||||
var commonNames struct {
|
||||
Localhost bool `structs:"localhost"`
|
||||
BaseDomain bool `structs:"example.com"`
|
||||
BareDomain bool `structs:"example.com"`
|
||||
SecondDomain bool `structs:"foobar.com"`
|
||||
SubDomain bool `structs:"foo.example.com"`
|
||||
Wildcard bool `structs:"*.example.com"`
|
||||
SubSubdomain bool `structs:"foo.bar.example.com"`
|
||||
|
@ -1129,9 +1130,9 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
|
|||
Bytes: csr,
|
||||
}
|
||||
issueVals.CSR = strings.TrimSpace(string(pem.EncodeToMemory(&block)))
|
||||
addTests(getCnCheck(issueVals.CommonName, roleVals.KeyType, privKey, usage, validity))
|
||||
addTests(getCnCheck(issueVals.CommonName, roleVals, privKey, usage, validity))
|
||||
} else {
|
||||
addTests(getCnCheck(issueVals.CommonName, roleVals.KeyType, nil, usage, validity))
|
||||
addTests(getCnCheck(issueVals.CommonName, roleVals, nil, usage, validity))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1150,10 +1151,10 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
|
|||
commonNames.Localhost = true
|
||||
addCnTests()
|
||||
|
||||
roleVals.AllowedBaseDomain = "foobar.com"
|
||||
roleVals.AllowedDomains = "foobar.com"
|
||||
addCnTests()
|
||||
|
||||
roleVals.AllowedBaseDomain = "example.com"
|
||||
roleVals.AllowedDomains = "example.com"
|
||||
roleVals.AllowSubdomains = true
|
||||
commonNames.SubDomain = true
|
||||
commonNames.Wildcard = true
|
||||
|
@ -1161,8 +1162,10 @@ func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep {
|
|||
commonNames.SubSubdomainWildcard = true
|
||||
addCnTests()
|
||||
|
||||
roleVals.AllowBaseDomain = true
|
||||
commonNames.BaseDomain = true
|
||||
roleVals.AllowedDomains = "foobar.com,example.com"
|
||||
commonNames.SecondDomain = true
|
||||
roleVals.AllowBareDomains = true
|
||||
commonNames.BareDomain = true
|
||||
addCnTests()
|
||||
|
||||
roleVals.AllowAnyName = true
|
||||
|
|
|
@ -237,7 +237,7 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) (strin
|
|||
|
||||
// The following blocks all work the same basic way:
|
||||
// 1) If a role allows a certain class of base (localhost, token
|
||||
// display name, role-configured base domain), perform further tests
|
||||
// display name, role-configured domains), perform further tests
|
||||
//
|
||||
// 2) If there is a perfect match on either the name itself or it's an
|
||||
// email address with a perfect match on the hostname portion, allow it
|
||||
|
@ -262,13 +262,13 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) (strin
|
|||
if role.AllowSubdomains {
|
||||
// It is possible, if unlikely, to have a subdomain of "localhost"
|
||||
if strings.HasSuffix(sanitizedName, ".localhost") ||
|
||||
(isWildcard && sanitizedName == role.AllowedBaseDomain) {
|
||||
(isWildcard && sanitizedName == "localhost") {
|
||||
continue
|
||||
}
|
||||
|
||||
// A subdomain of "localdomain" is also not entirely uncommon
|
||||
if strings.HasSuffix(sanitizedName, ".localdomain") ||
|
||||
(isWildcard && sanitizedName == role.AllowedBaseDomain) {
|
||||
(isWildcard && sanitizedName == "localdomain") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -298,29 +298,43 @@ func validateNames(req *logical.Request, names []string, role *roleEntry) (strin
|
|||
}
|
||||
|
||||
if strings.HasSuffix(sanitizedName, "."+req.DisplayName) ||
|
||||
(isWildcard && sanitizedName == role.AllowedBaseDomain) {
|
||||
(isWildcard && sanitizedName == req.DisplayName) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(role.AllowedBaseDomain) != 0 {
|
||||
// First, allow an exact match of the base domain if that role flag
|
||||
// is enabled
|
||||
if role.AllowBaseDomain &&
|
||||
(name == role.AllowedBaseDomain ||
|
||||
(isEmail && emailDomain == role.AllowedBaseDomain)) {
|
||||
if role.AllowedDomains != "" {
|
||||
valid := false
|
||||
for _, currDomain := range strings.Split(role.AllowedDomains, ",") {
|
||||
// If there is, say, a trailing comma, ignore it
|
||||
if currDomain == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// First, allow an exact match of the base domain if that role flag
|
||||
// is enabled
|
||||
if role.AllowBareDomains &&
|
||||
(name == currDomain ||
|
||||
(isEmail && emailDomain == currDomain)) {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
|
||||
if role.AllowSubdomains {
|
||||
if strings.HasSuffix(sanitizedName, "."+currDomain) ||
|
||||
(isWildcard && sanitizedName == currDomain) {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if valid {
|
||||
continue
|
||||
}
|
||||
|
||||
if role.AllowSubdomains {
|
||||
if strings.HasSuffix(sanitizedName, "."+role.AllowedBaseDomain) ||
|
||||
(isWildcard && sanitizedName == role.AllowedBaseDomain) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//panic(fmt.Sprintf("\nName is %s\nRole is\n%#v\n", name, role))
|
||||
return name, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package pki
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
|
@ -40,22 +41,23 @@ the value of max_ttl.`,
|
|||
name in a request`,
|
||||
},
|
||||
|
||||
"allowed_base_domain": &framework.FieldSchema{
|
||||
"allowed_domains": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "",
|
||||
Description: `If set, clients can request certificates for
|
||||
subdomains directly beneath this base domain, including
|
||||
the wildcard subdomain. See the documentation for more
|
||||
information. Note the difference between this and
|
||||
"allow_base_domain".`,
|
||||
subdomains directly beneath these domains, including
|
||||
the wildcard subdomains. See the documentation for more
|
||||
information. This parameter accepts a comma-separated list
|
||||
of domains.`,
|
||||
},
|
||||
|
||||
"allow_base_domain": &framework.FieldSchema{
|
||||
"allow_bare_domains": &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Default: false,
|
||||
Description: `If set, clients can request certificates
|
||||
for the base domain itself, e.g. "example.com". Note
|
||||
the difference between this and "allowed_base_domain".`,
|
||||
for the base domains themselves, e.g. "example.com".
|
||||
This is a separate option as in some cases this can
|
||||
be considered a security threat.`,
|
||||
},
|
||||
|
||||
"allow_subdomains": &framework.FieldSchema{
|
||||
|
@ -179,6 +181,33 @@ func (b *backend) getRole(s logical.Storage, n string) (*roleEntry, error) {
|
|||
result.LeaseMax = ""
|
||||
modified = true
|
||||
}
|
||||
if result.AllowBaseDomain {
|
||||
result.AllowBaseDomain = false
|
||||
result.AllowBareDomains = true
|
||||
modified = true
|
||||
}
|
||||
if result.AllowedBaseDomain != "" {
|
||||
found := false
|
||||
allowedDomains := strings.Split(result.AllowedDomains, ",")
|
||||
if len(allowedDomains) != 0 {
|
||||
for _, v := range allowedDomains {
|
||||
if v == result.AllowedBaseDomain {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if result.AllowedDomains == "" {
|
||||
result.AllowedDomains = result.AllowedBaseDomain
|
||||
} else {
|
||||
result.AllowedDomains += "," + result.AllowedBaseDomain
|
||||
}
|
||||
result.AllowedBaseDomain = ""
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
|
||||
if modified {
|
||||
jsonEntry, err := logical.StorageEntryJSON("role/"+n, &result)
|
||||
if err != nil {
|
||||
|
@ -241,8 +270,8 @@ func (b *backend) pathRoleCreate(
|
|||
MaxTTL: data.Get("max_ttl").(string),
|
||||
TTL: data.Get("ttl").(string),
|
||||
AllowLocalhost: data.Get("allow_localhost").(bool),
|
||||
AllowedBaseDomain: data.Get("allowed_base_domain").(string),
|
||||
AllowBaseDomain: data.Get("allow_base_domain").(bool),
|
||||
AllowedDomains: data.Get("allowed_domains").(string),
|
||||
AllowBareDomains: data.Get("allow_bare_domains").(bool),
|
||||
AllowSubdomains: data.Get("allow_subdomains").(bool),
|
||||
AllowAnyName: data.Get("allow_any_name").(bool),
|
||||
EnforceHostnames: data.Get("enforce_hostnames").(bool),
|
||||
|
@ -314,7 +343,9 @@ type roleEntry struct {
|
|||
TTL string `json:"ttl" structs:"ttl" mapstructure:"ttl"`
|
||||
AllowLocalhost bool `json:"allow_localhost" structs:"allow_localhost" mapstructure:"allow_localhost"`
|
||||
AllowedBaseDomain string `json:"allowed_base_domain" structs:"allowed_base_domain" mapstructure:"allowed_base_domain"`
|
||||
AllowedDomains string `json:"allowed_domains" structs:"allowed_domains" mapstructure:"allowed_domains"`
|
||||
AllowBaseDomain bool `json:"allow_base_domain" structs:"allow_base_domain" mapstructure:"allow_base_domain"`
|
||||
AllowBareDomains bool `json:"allow_bare_domains" structs:"allow_bare_domains" mapstructure:"allow_bare_domains"`
|
||||
AllowTokenDisplayName bool `json:"allow_token_displayname" structs:"allow_token_displayname" mapstructure:"allow_token_displayname"`
|
||||
AllowSubdomains bool `json:"allow_subdomains" structs:"allow_subdomains" mapstructure:"allow_subdomains"`
|
||||
AllowAnyName bool `json:"allow_any_name" structs:"allow_any_name" mapstructure:"allow_any_name"`
|
||||
|
|
|
@ -232,7 +232,7 @@ policy used to generated those credentials. For example, let's create an
|
|||
|
||||
```text
|
||||
$ vault write pki/roles/example-dot-com \
|
||||
allowed_base_domain="example.com" \
|
||||
allowed_domains="example.com" \
|
||||
allow_subdomains="true" max_ttl="72h"
|
||||
Success! Data written to: pki/roles/example-dot-com
|
||||
```
|
||||
|
@ -936,7 +936,7 @@ subpath for interactive help output.
|
|||
<dt>Description</dt>
|
||||
<dd>
|
||||
Creates or updates the role definition. Note that the
|
||||
`allowed_base_domain`, `allow_subdomains`, and
|
||||
`allowed_domains`, `allow_subdomains`, and
|
||||
`allow_any_name` attributes are additive; between them nearly and across
|
||||
multiple roles nearly any issuing policy can be accommodated.
|
||||
`server_flag`, `client_flag`, and `code_signing_flag` are additive as well.
|
||||
|
@ -975,25 +975,28 @@ subpath for interactive help output.
|
|||
on a single host to talk securely. Defaults to true.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">allowed_base_domain</span>
|
||||
<span class="param">allowed_domains</span>
|
||||
<span class="param-flags">optional</span>
|
||||
**N.B.**: In 0.4+, the meaning of this value has changed, although the
|
||||
name is kept for backwards compatibility.<br /><br />Designates the
|
||||
base domain of the role. This is used with the `allow_base_domain` and
|
||||
`allow_subdomains` options. There is no default.
|
||||
Designates the domains of the role, provided as a comma-separated list.
|
||||
This is used with the `allow_bare_domains` and `allow_subdomains`
|
||||
options. There is no default.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">allow_base_domain</span>
|
||||
<span class="param">allow_bare_domains</span>
|
||||
<span class="param-flags">optional</span>
|
||||
If set, clients can request certificates matching the value of the
|
||||
actual base domain. Defaults to false.
|
||||
actual domains themselves; e.g. if a configured domain set with
|
||||
`allowed_domains` is `example.com`, this allows clients to actually
|
||||
request a certificate containing the name `example.com` as one of the
|
||||
DNS values on the final certificate. In some scenarios, this can be
|
||||
considered a security risk. Defaults to false.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">allow_subdomains</span>
|
||||
<span class="param-flags">optional</span>
|
||||
If set, clients can request certificates with CNs that are subdomains
|
||||
of the CNs allowed by the other role options. _This includes wildcard
|
||||
subdomains._ For example, an `allowed_base_domain` value of
|
||||
subdomains._ For example, an `allowed_domains` value of
|
||||
`example.com` with this option set to true will allow `foo.example.com`
|
||||
and `bar.example.com` as well as `*.example.com`. This is redundant
|
||||
when using the `allow_any_name` option. Defaults to `false`.
|
||||
|
@ -1098,7 +1101,7 @@ subpath for interactive help output.
|
|||
"allow_ip_sans": true,
|
||||
"allow_localhost": true,
|
||||
"allow_subdomains": false,
|
||||
"allowed_base_domain": "example.com",
|
||||
"allowed_domains": "example.com,foobar.com",
|
||||
"client_flag": true,
|
||||
"code_signing_flag": false,
|
||||
"key_bits": 2048,
|
||||
|
|
Loading…
Reference in New Issue