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:
Jeff Mitchell 2015-11-30 23:49:11 -05:00
parent b6c49ddf01
commit 4eec9d69e8
5 changed files with 110 additions and 52 deletions

View File

@ -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:

View File

@ -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

View File

@ -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
}

View File

@ -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"`

View File

@ -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,