Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added URI Subject Alternative Names to secrets/pki #4675

Closed
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"math/big"
mathrand "math/rand"
"net"
"net/url"
"os"
"reflect"
"strconv"
Expand Down Expand Up @@ -3344,6 +3345,118 @@ func TestBackend_AllowedSerialNumbers(t *testing.T) {
t.Logf("certificate 2 to check:\n%s", certStr)
}

func TestBackend_URI_SANs(t *testing.T) {
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()

client := cluster.Cores[0].Client
var err error
err = client.Sys().Mount("root", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
})
if err != nil {
t.Fatal(err)
}

_, err = client.Logical().Write("root/root/generate/internal", map[string]interface{}{
"ttl": "40h",
"common_name": "myvault.com",
})
if err != nil {
t.Fatal(err)
}

_, err = client.Logical().Write("root/roles/test", map[string]interface{}{
"allowed_domains": []string{"foobar.com", "zipzap.com"},
"allow_bare_domains": true,
"allow_subdomains": true,
"allow_ip_sans": true,
"allowed_uri_sans": []string{"http://someuri/abc", "spiffe://host.com/*"},
})
if err != nil {
t.Fatal(err)
}

// First test some bad stuff that shouldn't work
_, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
"uri_sans": "http://www.mydomain.com/zxf",
})
if err == nil {
t.Fatal("expected error")
}

// Test valid single entry
_, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
"uri_sans": "http://someuri/abc",
})
if err != nil {
t.Fatal(err)
}

// Test globed entry
_, err = client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
"uri_sans": "spiffe://host.com/something",
})
if err != nil {
t.Fatal(err)
}

// Test multiple entries
resp, err := client.Logical().Write("root/issue/test", map[string]interface{}{
"common_name": "foobar.com",
"ip_sans": "1.2.3.4",
"alt_names": "foo.foobar.com,bar.foobar.com",
"ttl": "1h",
"uri_sans": "spiffe://host.com/something,http://someuri/abc",
})
if err != nil {
t.Fatal(err)
}

certStr := resp.Data["certificate"].(string)
block, _ := pem.Decode([]byte(certStr))
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatal(err)
}

URI0, _ := url.Parse("spiffe://host.com/something")
URI1, _ := url.Parse("http://someuri/abc")

if len(cert.URIs) != 2 {
t.Fatalf("expected 2 valid URIs SANs %v", cert.URIs)
}

if cert.URIs[0].String() != URI0.String() || cert.URIs[1].String() != URI1.String() {
t.Fatalf(
"expected URIs SANs %v to equal provided values spiffe://host.com/something, http://someuri/abc",
cert.URIs)
}
}
func setCerts() {
cak, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
Expand Down
76 changes: 76 additions & 0 deletions builtin/logical/pki/cert_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"encoding/pem"
"fmt"
"net"
"net/url"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -55,6 +56,7 @@ type creationParameters struct {
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP
URIs []*url.URL
OtherSANs map[string][]string
IsCA bool
KeyType string
Expand Down Expand Up @@ -882,6 +884,78 @@ func generateCreationBundle(b *backend, data *dataBundle) error {
}
}

URIs := []*url.URL{}
var uriAltInt interface{}
{
if data.csr != nil && data.role.UseCSRSANs {
if len(data.csr.URIs) > 0 {
if len(data.role.AllowedURISANs) == 0 {
return errutil.UserError{Err: fmt.Sprintf(
"URI Subject Alternative Names are not allowed in this role, but were provided via CSR"),
}
}

// validate uri sans
valid := false
for _, uri := range data.csr.URIs {
for _, allowed := range data.role.AllowedURISANs {
validURI := glob.Glob(allowed, uri.String())
if validURI {
valid = true
break
}
}
}

if !valid {
return errutil.UserError{Err: fmt.Sprintf(
"URI Subject Alternative Names were provided via CSR which are not valid for this role"),
}
}

URIs = data.csr.URIs
}
} else {
uriAltInt, ok = data.apiData.GetOk("uri_sans")
if ok {
uriAlt := uriAltInt.(string)
if len(uriAlt) != 0 {
if len(data.role.AllowedURISANs) == 0 {
return errutil.UserError{Err: fmt.Sprintf(
"URI Subject Alternative Names are not allowed in this role, but were provided via CSR"),
}
}

for _, v := range strings.Split(uriAlt, ",") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than do this, please use TypeCommaStringSlice for the parameter which will return you a []string already.

valid := false
for _, allowed := range data.role.AllowedURISANs {
validURI := glob.Glob(allowed, v)
if validURI {
valid = true
break
}
}

if !valid {
return errutil.UserError{Err: fmt.Sprintf(
"URI Subject Alternative Names were provided via CSR which are not valid for this role"),
}
}

parsedURI, err := url.Parse(v)
if parsedURI == nil || err != nil {
return errutil.UserError{Err: fmt.Sprintf(
"the provided URI Subject Alternative Name '%s' is not a valid URI", v),
}
}
URIs = append(URIs, parsedURI)

}
}
}
}
}

subject := pkix.Name{
CommonName: cn,
SerialNumber: ridSerialNumber,
Expand Down Expand Up @@ -953,6 +1027,7 @@ func generateCreationBundle(b *backend, data *dataBundle) error {
DNSNames: dnsNames,
EmailAddresses: emailAddresses,
IPAddresses: ipAddresses,
URIs: URIs,
OtherSANs: otherSANs,
KeyType: data.role.KeyType,
KeyBits: data.role.KeyBits,
Expand Down Expand Up @@ -1073,6 +1148,7 @@ func createCertificate(data *dataBundle) (*certutil.ParsedCertBundle, error) {
DNSNames: data.params.DNSNames,
EmailAddresses: data.params.EmailAddresses,
IPAddresses: data.params.IPAddresses,
URIs: data.params.URIs,
}

if err := handleOtherSANs(certTemplate, data.params.OtherSANs); err != nil {
Expand Down
6 changes: 6 additions & 0 deletions builtin/logical/pki/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ pkcs8 instead. Defaults to "der".`,
comma-delimited list`,
}

fields["uri_sans"] = &framework.FieldSchema{
Type: framework.TypeString,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be TypeCommaStringSlice

Description: `The requested URI SANs, if any, in a
comma-delimited list.`,
}

fields["other_sans"] = &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `Requested other SANs, in an array with the format
Expand Down
9 changes: 9 additions & 0 deletions builtin/logical/pki/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ CN and SANs. Defaults to true.`,
Any valid IP is accepted.`,
},

"allowed_uri_sans": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, an array of allowed URIs to put in the URI Subject Alternative Names.
Any valid URI is accepted, these values support globbing.`,
},

"allowed_other_sans": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `If set, an array of allowed other names to put in SANs. These values support globbing.`,
Expand Down Expand Up @@ -452,6 +458,7 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data
AllowAnyName: data.Get("allow_any_name").(bool),
EnforceHostnames: data.Get("enforce_hostnames").(bool),
AllowIPSANs: data.Get("allow_ip_sans").(bool),
AllowedURISANs: data.Get("allowed_uri_sans").([]string),
ServerFlag: data.Get("server_flag").(bool),
ClientFlag: data.Get("client_flag").(bool),
CodeSigningFlag: data.Get("code_signing_flag").(bool),
Expand Down Expand Up @@ -609,6 +616,7 @@ type roleEntry struct {
RequireCN bool `json:"require_cn" mapstructure:"require_cn"`
AllowedOtherSANs []string `json:"allowed_other_sans" mapstructure:"allowed_other_sans"`
AllowedSerialNumbers []string `json:"allowed_serial_numbers" mapstructure:"allowed_serial_numbers"`
AllowedURISANs []string `json:"allowed_uri_sans" mapstructure:"allowed_uri_sans"`
PolicyIdentifiers []string `json:"policy_identifiers" mapstructure:"policy_identifiers"`
ExtKeyUsageOIDs []string `json:"ext_key_usage_oids" mapstructure:"ext_key_usage_oids"`
BasicConstraintsValidForNonCA bool `json:"basic_constraints_valid_for_non_ca" mapstructure:"basic_constraints_valid_for_non_ca"`
Expand Down Expand Up @@ -650,6 +658,7 @@ func (r *roleEntry) ToResponseData() map[string]interface{} {
"no_store": r.NoStore,
"allowed_other_sans": r.AllowedOtherSANs,
"allowed_serial_numbers": r.AllowedSerialNumbers,
"allowed_uri_sans": r.AllowedURISANs,
"require_cn": r.RequireCN,
"policy_identifiers": r.PolicyIdentifiers,
"basic_constraints_valid_for_non_ca": r.BasicConstraintsValidForNonCA,
Expand Down
34 changes: 34 additions & 0 deletions builtin/logical/pki/path_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,40 @@ func TestPki_RoleAllowedDomains(t *testing.T) {
}
}

func TestPki_RoleAllowedURISANs(t *testing.T) {
var resp *logical.Response
var err error
b, storage := createBackendWithStorage(t)

roleData := map[string]interface{}{
"allowed_uri_sans": []string{"http://foobar.com", "spiffe://*"},
"ttl": "5h",
}

roleReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/testrole",
Storage: storage,
Data: roleData,
}

resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v resp: %#v", err, resp)
}

roleReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v resp: %#v", err, resp)
}

allowedURISANs := resp.Data["allowed_uri_sans"].([]string)
if len(allowedURISANs) != 2 {
t.Fatalf("allowed_uri_sans should have 2 values")
}
}

func TestPki_RolePkixFields(t *testing.T) {
var resp *logical.Response
var err error
Expand Down
Loading