Merge branch 'oss' into sys-tidy-leases
This commit is contained in:
commit
60add30b9d
|
@ -21,6 +21,8 @@ FEATURES:
|
|||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* auth/cert: Support for constraints on subject common name, DNS names and
|
||||
Email addresses in the certificate [GH-2595]
|
||||
* auth/ldap: Use the binding credentials to search group membership rather
|
||||
than the user credentials [GH-2534]
|
||||
* cli/revoke: Add `-self` option to allow revoking the currently active token
|
||||
|
|
|
@ -348,9 +348,9 @@ func TestBackend_CertWrites(t *testing.T) {
|
|||
tc := logicaltest.TestCase{
|
||||
Backend: testFactory(t),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepCert(t, "aaa", ca1, "foo", false),
|
||||
testAccStepCert(t, "bbb", ca2, "foo", false),
|
||||
testAccStepCert(t, "ccc", ca3, "foo", true),
|
||||
testAccStepCert(t, "aaa", ca1, "foo", "", false),
|
||||
testAccStepCert(t, "bbb", ca2, "foo", "", false),
|
||||
testAccStepCert(t, "ccc", ca3, "foo", "", true),
|
||||
},
|
||||
}
|
||||
tc.Steps = append(tc.Steps, testAccStepListCerts(t, []string{"aaa", "bbb"})...)
|
||||
|
@ -368,13 +368,17 @@ func TestBackend_basic_CA(t *testing.T) {
|
|||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: testFactory(t),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepCert(t, "web", ca, "foo", false),
|
||||
testAccStepCert(t, "web", ca, "foo", "", false),
|
||||
testAccStepLogin(t, connState),
|
||||
testAccStepCertLease(t, "web", ca, "foo"),
|
||||
testAccStepCertTTL(t, "web", ca, "foo"),
|
||||
testAccStepLogin(t, connState),
|
||||
testAccStepCertNoLease(t, "web", ca, "foo"),
|
||||
testAccStepLoginDefaultLease(t, connState),
|
||||
testAccStepCert(t, "web", ca, "foo", "*.example.com", false),
|
||||
testAccStepLogin(t, connState),
|
||||
testAccStepCert(t, "web", ca, "foo", "*.invalid.com", false),
|
||||
testAccStepLoginInvalid(t, connState),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -405,8 +409,29 @@ func TestBackend_Basic_CRLs(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// Test a self-signed client that is trusted
|
||||
// Test a self-signed client (root CA) that is trusted
|
||||
func TestBackend_basic_singleCert(t *testing.T) {
|
||||
connState := testConnState(t, "test-fixtures/root/rootcacert.pem",
|
||||
"test-fixtures/root/rootcakey.pem", "test-fixtures/root/rootcacert.pem")
|
||||
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: testFactory(t),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepCert(t, "web", ca, "foo", "", false),
|
||||
testAccStepLogin(t, connState),
|
||||
testAccStepCert(t, "web", ca, "foo", "example.com", false),
|
||||
testAccStepLogin(t, connState),
|
||||
testAccStepCert(t, "web", ca, "foo", "invalid", false),
|
||||
testAccStepLoginInvalid(t, connState),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Test against a collection of matching and non-matching rules
|
||||
func TestBackend_mixed_constraints(t *testing.T) {
|
||||
connState := testConnState(t, "test-fixtures/keys/cert.pem",
|
||||
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
|
||||
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
|
||||
|
@ -416,13 +441,18 @@ func TestBackend_basic_singleCert(t *testing.T) {
|
|||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: testFactory(t),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepCert(t, "web", ca, "foo", false),
|
||||
testAccStepCert(t, "1unconstrained", ca, "foo", "", false),
|
||||
testAccStepCert(t, "2matching", ca, "foo", "*.example.com,whatever", false),
|
||||
testAccStepCert(t, "3invalid", ca, "foo", "invalid", false),
|
||||
testAccStepLogin(t, connState),
|
||||
// Assumes CertEntries are processed in alphabetical order (due to store.List), so we only match 2matching if 1unconstrained doesn't match
|
||||
testAccStepLoginWithName(t, connState, "2matching"),
|
||||
testAccStepLoginWithNameInvalid(t, connState, "3invalid"),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Test an untrusted self-signed client
|
||||
// Test an untrusted client
|
||||
func TestBackend_untrusted(t *testing.T) {
|
||||
connState := testConnState(t, "test-fixtures/keys/cert.pem",
|
||||
"test-fixtures/keys/key.pem", "test-fixtures/root/rootcacert.pem")
|
||||
|
@ -476,6 +506,10 @@ func testAccStepDeleteCRL(t *testing.T, connState tls.ConnectionState) logicalte
|
|||
}
|
||||
|
||||
func testAccStepLogin(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
|
||||
return testAccStepLoginWithName(t, connState, "")
|
||||
}
|
||||
|
||||
func testAccStepLoginWithName(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "login",
|
||||
|
@ -486,9 +520,16 @@ func testAccStepLogin(t *testing.T, connState tls.ConnectionState) logicaltest.T
|
|||
t.Fatalf("bad lease length: %#v", resp.Auth)
|
||||
}
|
||||
|
||||
if certName != "" && resp.Auth.DisplayName != ("mnt-"+certName) {
|
||||
t.Fatalf("matched the wrong cert: %#v", resp.Auth.DisplayName)
|
||||
}
|
||||
|
||||
fn := logicaltest.TestCheckAuth([]string{"default", "foo"})
|
||||
return fn(resp)
|
||||
},
|
||||
Data: map[string]interface{}{
|
||||
"name": certName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,6 +551,10 @@ func testAccStepLoginDefaultLease(t *testing.T, connState tls.ConnectionState) l
|
|||
}
|
||||
|
||||
func testAccStepLoginInvalid(t *testing.T, connState tls.ConnectionState) logicaltest.TestStep {
|
||||
return testAccStepLoginWithNameInvalid(t, connState, "")
|
||||
}
|
||||
|
||||
func testAccStepLoginWithNameInvalid(t *testing.T, connState tls.ConnectionState, certName string) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "login",
|
||||
|
@ -521,6 +566,9 @@ func testAccStepLoginInvalid(t *testing.T, connState tls.ConnectionState) logica
|
|||
}
|
||||
return nil
|
||||
},
|
||||
Data: map[string]interface{}{
|
||||
"name": certName,
|
||||
},
|
||||
ErrorOk: true,
|
||||
}
|
||||
}
|
||||
|
@ -572,16 +620,17 @@ func testAccStepListCerts(
|
|||
}
|
||||
|
||||
func testAccStepCert(
|
||||
t *testing.T, name string, cert []byte, policies string, expectError bool) logicaltest.TestStep {
|
||||
t *testing.T, name string, cert []byte, policies string, allowedNames string, expectError bool) logicaltest.TestStep {
|
||||
return logicaltest.TestStep{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "certs/" + name,
|
||||
ErrorOk: expectError,
|
||||
Data: map[string]interface{}{
|
||||
"certificate": string(cert),
|
||||
"policies": policies,
|
||||
"display_name": name,
|
||||
"lease": 1000,
|
||||
"certificate": string(cert),
|
||||
"policies": policies,
|
||||
"display_name": name,
|
||||
"allowed_names": allowedNames,
|
||||
"lease": 1000,
|
||||
},
|
||||
Check: func(resp *logical.Response) error {
|
||||
if resp == nil && expectError {
|
||||
|
@ -730,10 +779,17 @@ func Test_Renew(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err = b.pathLogin(req, nil)
|
||||
empty_login_fd := &framework.FieldData{
|
||||
Raw: map[string]interface{}{},
|
||||
Schema: pathLogin(b).Fields,
|
||||
}
|
||||
resp, err = b.pathLogin(req, empty_login_fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.IsError() {
|
||||
t.Fatalf("got error: %#v", *resp)
|
||||
}
|
||||
req.Auth.InternalData = resp.Auth.InternalData
|
||||
req.Auth.Metadata = resp.Auth.Metadata
|
||||
req.Auth.LeaseOptions = resp.Auth.LeaseOptions
|
||||
|
@ -741,7 +797,7 @@ func Test_Renew(t *testing.T) {
|
|||
req.Auth.IssueTime = time.Now()
|
||||
|
||||
// Normal renewal
|
||||
resp, err = b.pathLoginRenew(req, nil)
|
||||
resp, err = b.pathLoginRenew(req, empty_login_fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -759,7 +815,7 @@ func Test_Renew(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err = b.pathLoginRenew(req, nil)
|
||||
resp, err = b.pathLoginRenew(req, empty_login_fd)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
@ -771,7 +827,7 @@ func Test_Renew(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err = b.pathLoginRenew(req, nil)
|
||||
resp, err = b.pathLoginRenew(req, empty_login_fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -788,7 +844,7 @@ func Test_Renew(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err = b.pathLoginRenew(req, nil)
|
||||
resp, err = b.pathLoginRenew(req, empty_login_fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ type CLIHandler struct{}
|
|||
func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
|
||||
var data struct {
|
||||
Mount string `mapstructure:"mount"`
|
||||
Name string `mapstructure:"name"`
|
||||
}
|
||||
if err := mapstructure.WeakDecode(m, &data); err != nil {
|
||||
return "", err
|
||||
|
@ -22,8 +23,11 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (string, error) {
|
|||
data.Mount = "cert"
|
||||
}
|
||||
|
||||
options := map[string]interface{}{
|
||||
"name": data.Name,
|
||||
}
|
||||
path := fmt.Sprintf("auth/%s/login", data.Mount)
|
||||
secret, err := c.Logical().Write(path, nil)
|
||||
secret, err := c.Logical().Write(path, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -38,10 +42,13 @@ func (h *CLIHandler) Help() string {
|
|||
help := `
|
||||
The "cert" credential provider allows you to authenticate with a
|
||||
client certificate. No other authentication materials are needed.
|
||||
Optionally, you may specify the specific certificate role to
|
||||
authenticate against with the "name" parameter.
|
||||
|
||||
Example: vault auth -method=cert \
|
||||
-client-cert=/path/to/cert.pem \
|
||||
-client-key=/path/to/key.pem
|
||||
name=cert1
|
||||
|
||||
`
|
||||
|
||||
|
|
|
@ -39,6 +39,12 @@ func pathCerts(b *backend) *framework.Path {
|
|||
Must be x509 PEM encoded.`,
|
||||
},
|
||||
|
||||
"allowed_names": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `A comma-separated list of names.
|
||||
At least one must exist in either the Common Name or SANs. Supports globbing.`,
|
||||
},
|
||||
|
||||
"display_name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `The display name to use for clients using this
|
||||
|
@ -139,6 +145,7 @@ func (b *backend) pathCertWrite(
|
|||
certificate := d.Get("certificate").(string)
|
||||
displayName := d.Get("display_name").(string)
|
||||
policies := policyutil.ParsePolicies(d.Get("policies").(string))
|
||||
allowedNames := d.Get("allowed_names").([]string)
|
||||
|
||||
// Default the display name to the certificate name if not given
|
||||
if displayName == "" {
|
||||
|
@ -165,10 +172,11 @@ func (b *backend) pathCertWrite(
|
|||
}
|
||||
|
||||
certEntry := &CertEntry{
|
||||
Name: name,
|
||||
Certificate: certificate,
|
||||
DisplayName: displayName,
|
||||
Policies: policies,
|
||||
Name: name,
|
||||
Certificate: certificate,
|
||||
DisplayName: displayName,
|
||||
Policies: policies,
|
||||
AllowedNames: allowedNames,
|
||||
}
|
||||
|
||||
// Parse the lease duration or default to backend/system default
|
||||
|
@ -196,11 +204,12 @@ func (b *backend) pathCertWrite(
|
|||
}
|
||||
|
||||
type CertEntry struct {
|
||||
Name string
|
||||
Certificate string
|
||||
DisplayName string
|
||||
Policies []string
|
||||
TTL time.Duration
|
||||
Name string
|
||||
Certificate string
|
||||
DisplayName string
|
||||
Policies []string
|
||||
TTL time.Duration
|
||||
AllowedNames []string
|
||||
}
|
||||
|
||||
const pathCertHelpSyn = `
|
||||
|
|
|
@ -14,6 +14,8 @@ import (
|
|||
"github.com/hashicorp/vault/helper/policyutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
|
||||
"github.com/ryanuber/go-glob"
|
||||
)
|
||||
|
||||
// ParsedCert is a certificate that has been configured as trusted
|
||||
|
@ -25,7 +27,12 @@ type ParsedCert struct {
|
|||
func pathLogin(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "login",
|
||||
Fields: map[string]*framework.FieldSchema{},
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "The name of the certificate role to authenticate against.",
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathLogin,
|
||||
},
|
||||
|
@ -36,7 +43,7 @@ func (b *backend) pathLogin(
|
|||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
|
||||
var matched *ParsedCert
|
||||
if verifyResp, resp, err := b.verifyCredentials(req); err != nil {
|
||||
if verifyResp, resp, err := b.verifyCredentials(req, data); err != nil {
|
||||
return nil, err
|
||||
} else if resp != nil {
|
||||
return resp, nil
|
||||
|
@ -93,7 +100,7 @@ func (b *backend) pathLoginRenew(
|
|||
|
||||
if !config.DisableBinding {
|
||||
var matched *ParsedCert
|
||||
if verifyResp, resp, err := b.verifyCredentials(req); err != nil {
|
||||
if verifyResp, resp, err := b.verifyCredentials(req, d); err != nil {
|
||||
return nil, err
|
||||
} else if resp != nil {
|
||||
return resp, nil
|
||||
|
@ -136,7 +143,7 @@ func (b *backend) pathLoginRenew(
|
|||
return framework.LeaseExtend(cert.TTL, 0, b.System())(req, d)
|
||||
}
|
||||
|
||||
func (b *backend) verifyCredentials(req *logical.Request) (*ParsedCert, *logical.Response, error) {
|
||||
func (b *backend) verifyCredentials(req *logical.Request, d *framework.FieldData) (*ParsedCert, *logical.Response, error) {
|
||||
// Get the connection state
|
||||
if req.Connection == nil || req.Connection.ConnState == nil {
|
||||
return nil, logical.ErrorResponse("tls connection required"), nil
|
||||
|
@ -146,20 +153,29 @@ func (b *backend) verifyCredentials(req *logical.Request) (*ParsedCert, *logical
|
|||
if connState.PeerCertificates == nil || len(connState.PeerCertificates) == 0 {
|
||||
return nil, logical.ErrorResponse("client certificate must be supplied"), nil
|
||||
}
|
||||
clientCert := connState.PeerCertificates[0]
|
||||
|
||||
// Allow constraining the login request to a single CertEntry
|
||||
certName := d.Get("name").(string)
|
||||
|
||||
// Load the trusted certificates
|
||||
roots, trusted, trustedNonCAs := b.loadTrustedCerts(req.Storage)
|
||||
roots, trusted, trustedNonCAs := b.loadTrustedCerts(req.Storage, certName)
|
||||
|
||||
// If trustedNonCAs is not empty it means that client had registered a non-CA cert
|
||||
// with the backend.
|
||||
if len(trustedNonCAs) != 0 {
|
||||
policy := b.matchNonCAPolicy(connState.PeerCertificates[0], trustedNonCAs)
|
||||
if policy != nil && !b.checkForChainInCRLs(policy.Certificates) {
|
||||
return policy, nil, nil
|
||||
for _, trustedNonCA := range trustedNonCAs {
|
||||
tCert := trustedNonCA.Certificates[0]
|
||||
// Check for client cert being explicitly listed in the config (and matching other constraints)
|
||||
if tCert.SerialNumber.Cmp(clientCert.SerialNumber) == 0 &&
|
||||
bytes.Equal(tCert.AuthorityKeyId, clientCert.AuthorityKeyId) &&
|
||||
b.matchesConstraints(clientCert, trustedNonCA.Certificates, trustedNonCA) {
|
||||
return trustedNonCA, nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the connection state is trusted
|
||||
// Get the list of full chains matching the connection
|
||||
trustedChains, err := validateConnState(roots, connState)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -170,49 +186,58 @@ func (b *backend) verifyCredentials(req *logical.Request) (*ParsedCert, *logical
|
|||
return nil, logical.ErrorResponse("invalid certificate or no client certificate supplied"), nil
|
||||
}
|
||||
|
||||
validChain := b.checkForValidChain(trustedChains)
|
||||
if !validChain {
|
||||
return nil, logical.ErrorResponse(
|
||||
"no chain containing non-revoked certificates could be found for this login certificate",
|
||||
), nil
|
||||
}
|
||||
|
||||
// Match the trusted chain with the policy
|
||||
return b.matchPolicy(trustedChains, trusted), nil, nil
|
||||
}
|
||||
|
||||
// matchNonCAPolicy is used to match the client cert with the registered non-CA
|
||||
// policies to establish client identity.
|
||||
func (b *backend) matchNonCAPolicy(clientCert *x509.Certificate, trustedNonCAs []*ParsedCert) *ParsedCert {
|
||||
for _, trustedNonCA := range trustedNonCAs {
|
||||
tCert := trustedNonCA.Certificates[0]
|
||||
if tCert.SerialNumber.Cmp(clientCert.SerialNumber) == 0 && bytes.Equal(tCert.AuthorityKeyId, clientCert.AuthorityKeyId) {
|
||||
return trustedNonCA
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchPolicy is used to match the associated policy with the certificate that
|
||||
// was used to establish the client identity.
|
||||
func (b *backend) matchPolicy(chains [][]*x509.Certificate, trusted []*ParsedCert) *ParsedCert {
|
||||
// There is probably a better way to do this...
|
||||
for _, chain := range chains {
|
||||
for _, trust := range trusted {
|
||||
for _, tCert := range trust.Certificates {
|
||||
for _, cCert := range chain {
|
||||
if tCert.Equal(cCert) {
|
||||
return trust
|
||||
// Search for a ParsedCert that intersects with the validated chains and any additional constraints
|
||||
matches := make([]*ParsedCert, 0)
|
||||
for _, trust := range trusted { // For each ParsedCert in the config
|
||||
for _, tCert := range trust.Certificates { // For each certificate in the entry
|
||||
for _, chain := range trustedChains { // For each root chain that we matched
|
||||
for _, cCert := range chain { // For each cert in the matched chain
|
||||
if tCert.Equal(cCert) && // ParsedCert intersects with matched chain
|
||||
b.matchesConstraints(clientCert, chain, trust) { // validate client cert + matched chain against the config
|
||||
// Add the match to the list
|
||||
matches = append(matches, trust)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
// Fail on no matches
|
||||
if len(matches) == 0 {
|
||||
return nil, logical.ErrorResponse("no chain matching all constraints could be found for this login certificate"), nil
|
||||
}
|
||||
|
||||
// Return the first matching entry (for backwards compatibility, we continue to just pick one if multiple match)
|
||||
return matches[0], nil, nil
|
||||
}
|
||||
|
||||
func (b *backend) matchesConstraints(clientCert *x509.Certificate, trustedChain []*x509.Certificate, config *ParsedCert) bool {
|
||||
// Default behavior (no names) is to allow all names
|
||||
nameMatched := len(config.Entry.AllowedNames) == 0
|
||||
// At least one pattern must match at least one name if any patterns are specified
|
||||
for _, allowedName := range config.Entry.AllowedNames {
|
||||
if glob.Glob(allowedName, clientCert.Subject.CommonName) {
|
||||
nameMatched = true
|
||||
}
|
||||
|
||||
for _, name := range clientCert.DNSNames {
|
||||
if glob.Glob(allowedName, name) {
|
||||
nameMatched = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range clientCert.EmailAddresses {
|
||||
if glob.Glob(allowedName, name) {
|
||||
nameMatched = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !b.checkForChainInCRLs(trustedChain) && nameMatched
|
||||
}
|
||||
|
||||
// loadTrustedCerts is used to load all the trusted certificates from the backend
|
||||
func (b *backend) loadTrustedCerts(store logical.Storage) (pool *x509.CertPool, trusted []*ParsedCert, trustedNonCAs []*ParsedCert) {
|
||||
func (b *backend) loadTrustedCerts(store logical.Storage, certName string) (pool *x509.CertPool, trusted []*ParsedCert, trustedNonCAs []*ParsedCert) {
|
||||
pool = x509.NewCertPool()
|
||||
trusted = make([]*ParsedCert, 0)
|
||||
trustedNonCAs = make([]*ParsedCert, 0)
|
||||
|
@ -222,6 +247,10 @@ func (b *backend) loadTrustedCerts(store logical.Storage) (pool *x509.CertPool,
|
|||
return
|
||||
}
|
||||
for _, name := range names {
|
||||
// If we are trying to select a single CertEntry and this isn't it
|
||||
if certName != "" && name != certName {
|
||||
continue
|
||||
}
|
||||
entry, err := b.Cert(store, strings.TrimPrefix(name, "cert/"))
|
||||
if err != nil {
|
||||
b.Logger().Error("cert: failed to load trusted cert", "name", name, "error", err)
|
||||
|
|
|
@ -466,7 +466,7 @@ func signCert(b *backend,
|
|||
}
|
||||
csr, err := x509.ParseCertificateRequest(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, errutil.UserError{Err: "certificate request could not be parsed"}
|
||||
return nil, errutil.UserError{Err: fmt.Sprintf("certificate request could not be parsed: %v", err)}
|
||||
}
|
||||
|
||||
switch role.KeyType {
|
||||
|
@ -970,7 +970,7 @@ func createCSR(creationInfo *creationBundle) (*certutil.ParsedCSRBundle, error)
|
|||
result.CSRBytes = csr
|
||||
result.CSR, err = x509.ParseCertificateRequest(csr)
|
||||
if err != nil {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %s", err)}
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse created certificate: %v", err)}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
|
|
@ -444,7 +444,7 @@ func (c *CSRBundle) ToParsedCSRBundle() (*ParsedCSRBundle, error) {
|
|||
result.CSRBytes = pemBlock.Bytes
|
||||
result.CSR, err = x509.ParseCertificateRequest(result.CSRBytes)
|
||||
if err != nil {
|
||||
return nil, errutil.UserError{"Error encountered parsing certificate bytes from raw bundle"}
|
||||
return nil, errutil.UserError{fmt.Sprintf("Error encountered parsing certificate bytes from raw bundle: %v", err)}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
mathrand "math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -166,13 +168,17 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
|
|||
return nil, fmt.Errorf("cannot split an empty secret")
|
||||
}
|
||||
|
||||
// Generate random list of x coordinates
|
||||
mathrand.Seed(time.Now().UnixNano())
|
||||
xCoordinates := mathrand.Perm(255)
|
||||
|
||||
// Allocate the output array, initialize the final byte
|
||||
// of the output with the offset. The representation of each
|
||||
// output is {y1, y2, .., yN, x}.
|
||||
out := make([][]byte, parts)
|
||||
for idx := range out {
|
||||
out[idx] = make([]byte, len(secret)+1)
|
||||
out[idx][len(secret)] = uint8(idx) + 1
|
||||
out[idx][len(secret)] = uint8(xCoordinates[idx]) + 1
|
||||
}
|
||||
|
||||
// Construct a random polynomial for each byte of the secret.
|
||||
|
@ -189,7 +195,7 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
|
|||
// We cheat by encoding the x value once as the final index,
|
||||
// so that it only needs to be stored once.
|
||||
for i := 0; i < parts; i++ {
|
||||
x := uint8(i) + 1
|
||||
x := uint8(xCoordinates[i]) + 1
|
||||
y := p.evaluate(x)
|
||||
out[i][idx] = y
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Ryan Uber
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,29 @@
|
|||
# String globbing in golang [![Build Status](https://travis-ci.org/ryanuber/go-glob.svg)](https://travis-ci.org/ryanuber/go-glob)
|
||||
|
||||
`go-glob` is a single-function library implementing basic string glob support.
|
||||
|
||||
Globs are an extremely user-friendly way of supporting string matching without
|
||||
requiring knowledge of regular expressions or Go's particular regex engine. Most
|
||||
people understand that if you put a `*` character somewhere in a string, it is
|
||||
treated as a wildcard. Surprisingly, this functionality isn't found in Go's
|
||||
standard library, except for `path.Match`, which is intended to be used while
|
||||
comparing paths (not arbitrary strings), and contains specialized logic for this
|
||||
use case. A better solution might be a POSIX basic (non-ERE) regular expression
|
||||
engine for Go, which doesn't exist currently.
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import "github.com/ryanuber/go-glob"
|
||||
|
||||
func main() {
|
||||
glob.Glob("*World!", "Hello, World!") // true
|
||||
glob.Glob("Hello,*", "Hello, World!") // true
|
||||
glob.Glob("*ello,*", "Hello, World!") // true
|
||||
glob.Glob("World!", "Hello, World!") // false
|
||||
glob.Glob("/home/*", "/home/ryanuber/.bashrc") // true
|
||||
}
|
||||
```
|
|
@ -0,0 +1,56 @@
|
|||
package glob
|
||||
|
||||
import "strings"
|
||||
|
||||
// The character which is treated like a glob
|
||||
const GLOB = "*"
|
||||
|
||||
// Glob will test a string pattern, potentially containing globs, against a
|
||||
// subject string. The result is a simple true/false, determining whether or
|
||||
// not the glob pattern matched the subject text.
|
||||
func Glob(pattern, subj string) bool {
|
||||
// Empty pattern can only match empty subject
|
||||
if pattern == "" {
|
||||
return subj == pattern
|
||||
}
|
||||
|
||||
// If the pattern _is_ a glob, it matches everything
|
||||
if pattern == GLOB {
|
||||
return true
|
||||
}
|
||||
|
||||
parts := strings.Split(pattern, GLOB)
|
||||
|
||||
if len(parts) == 1 {
|
||||
// No globs in pattern, so test for equality
|
||||
return subj == pattern
|
||||
}
|
||||
|
||||
leadingGlob := strings.HasPrefix(pattern, GLOB)
|
||||
trailingGlob := strings.HasSuffix(pattern, GLOB)
|
||||
end := len(parts) - 1
|
||||
|
||||
// Go over the leading parts and ensure they match.
|
||||
for i := 0; i < end; i++ {
|
||||
idx := strings.Index(subj, parts[i])
|
||||
|
||||
switch i {
|
||||
case 0:
|
||||
// Check the first section. Requires special handling.
|
||||
if !leadingGlob && idx != 0 {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
// Check that the middle parts match.
|
||||
if idx < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Trim evaluated text from subj as we loop over the pattern.
|
||||
subj = subj[idx+len(parts[i]):]
|
||||
}
|
||||
|
||||
// Reached the last section. Requires special handling.
|
||||
return trailingGlob || strings.HasSuffix(subj, parts[end])
|
||||
}
|
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
@ -84,7 +83,7 @@ func (c *Client) call(endpoint, method string, request, response interface{}) er
|
|||
var url = "https://" + c.org + "." + c.Url + "/api/v1/" + endpoint
|
||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Add("Accept", `application/json`)
|
||||
|
@ -95,19 +94,19 @@ func (c *Client) call(endpoint, method string, request, response interface{}) er
|
|||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
err := json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var errors ErrorResponse
|
||||
|
|
|
@ -1158,6 +1158,12 @@
|
|||
"revision": "ddeb643de91b4ee0d9d87172c931a4ea3d81d49a",
|
||||
"revisionTime": "2017-02-08T17:17:27Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "6JP37UqrI0H80Gpk0Y2P+KXgn5M=",
|
||||
"path": "github.com/ryanuber/go-glob",
|
||||
"revision": "256dc444b735e061061cf46c809487313d5b0065",
|
||||
"revisionTime": "2017-01-28T01:21:29Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "5SYLEhADhdBVZAGPVHWggQl7H8k=",
|
||||
"path": "github.com/samuel/go-zookeeper/zk",
|
||||
|
@ -1171,10 +1177,10 @@
|
|||
"revisionTime": "2017-04-08T21:24:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "reJ+wO9qzH/7r2vXQE5MiTvg8+w=",
|
||||
"checksumSHA1": "7b7psq20O8IOCr885W2Ld6a3KTc=",
|
||||
"path": "github.com/sstarcher/go-okta",
|
||||
"revision": "388b6aef4eed400621bd3e3a98d831ef1368582d",
|
||||
"revisionTime": "2016-10-03T17:19:47Z"
|
||||
"revision": "64b3cb9e3a7b6d0c4e4432576c873e492d152666",
|
||||
"revisionTime": "2017-04-28T20:44:25Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "MxLnUmfrP+r5HfCZM29+WPKebn8=",
|
||||
|
|
|
@ -15,6 +15,7 @@ var (
|
|||
|
||||
Version = "unknown"
|
||||
VersionPrerelease = "unknown"
|
||||
VersionMetadata = ""
|
||||
)
|
||||
|
||||
// VersionInfo
|
||||
|
@ -22,11 +23,13 @@ type VersionInfo struct {
|
|||
Revision string
|
||||
Version string
|
||||
VersionPrerelease string
|
||||
VersionMetadata string
|
||||
}
|
||||
|
||||
func GetVersion() *VersionInfo {
|
||||
ver := Version
|
||||
rel := VersionPrerelease
|
||||
md := VersionMetadata
|
||||
if GitDescribe != "" {
|
||||
ver = GitDescribe
|
||||
}
|
||||
|
@ -38,6 +41,7 @@ func GetVersion() *VersionInfo {
|
|||
Revision: GitCommit,
|
||||
Version: ver,
|
||||
VersionPrerelease: rel,
|
||||
VersionMetadata: md,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,6 +56,10 @@ func (c *VersionInfo) VersionNumber() string {
|
|||
version = fmt.Sprintf("%s-%s", version, c.VersionPrerelease)
|
||||
}
|
||||
|
||||
if c.VersionMetadata != "" {
|
||||
version = fmt.Sprintf("%s+%s", version, c.VersionMetadata)
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
|
@ -66,6 +74,11 @@ func (c *VersionInfo) FullVersionNumber(rev bool) string {
|
|||
if c.VersionPrerelease != "" {
|
||||
fmt.Fprintf(&versionString, "-%s", c.VersionPrerelease)
|
||||
}
|
||||
|
||||
if c.VersionMetadata != "" {
|
||||
fmt.Fprintf(&versionString, "+%s", c.VersionMetadata)
|
||||
}
|
||||
|
||||
if rev && c.Revision != "" {
|
||||
fmt.Fprintf(&versionString, " (%s)", c.Revision)
|
||||
}
|
||||
|
|
|
@ -71,6 +71,13 @@ single word name or a more complex, nested path.
|
|||
|
||||
- `type` `(string: <required>)` – Specifies the type of the audit backend.
|
||||
|
||||
Additionally, the following options are allowed in Vault open-source, but
|
||||
relevant functionality is only supported in Vault Enterprise:
|
||||
|
||||
- `local` `(bool: false)` – Specifies if the audit backend is a local mount
|
||||
only. Local mounts are not replicated nor (if a secondary) removed by
|
||||
replication.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
|
|
@ -74,6 +74,13 @@ For example, mounting the "foo" auth backend will make it accessible at
|
|||
- `type` `(string: <required>)` – Specifies the name of the authentication
|
||||
backend type, such as "github" or "token".
|
||||
|
||||
Additionally, the following options are allowed in Vault open-source, but
|
||||
relevant functionality is only supported in Vault Enterprise:
|
||||
|
||||
- `local` `(bool: false)` – Specifies if the auth backend is a local mount
|
||||
only. Local mounts are not replicated nor (if a secondary) removed by
|
||||
replication.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
|
|
@ -84,6 +84,13 @@ This endpoint mounts a new secret backend at the given path.
|
|||
disabling backend caching respectively. If set on a specific mount, this
|
||||
overrides the global defaults.
|
||||
|
||||
Additionally, the following options are allowed in Vault open-source, but
|
||||
relevant functionality is only supported in Vault Enterprise:
|
||||
|
||||
- `local` `(bool: false)` – Specifies if the secret backend is a local mount
|
||||
only. Local mounts are not replicated nor (if a secondary) removed by
|
||||
replication.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
|
|
@ -41,6 +41,7 @@ token.
|
|||
```
|
||||
$ curl \
|
||||
--header "X-Vault-Token: ..." \
|
||||
--header "X-Vault-Wrap-TTL: 60" \
|
||||
--request POST \
|
||||
--data @payload.json \
|
||||
https://vault.rocks/v1/sys/wrapping/wrap
|
||||
|
|
|
@ -60,18 +60,25 @@ it is up to the administrator to remove it from the backend.
|
|||
## Authentication
|
||||
|
||||
### Via the CLI
|
||||
The below requires Vault to present a certificate signed by `ca.pem` and
|
||||
presents `cert.pem` (using `key.pem`) to authenticate against the `web` cert
|
||||
role. If a certificate role name is not specified, the auth backend will try to
|
||||
authenticate against all trusted certificates.
|
||||
|
||||
```
|
||||
$ vault auth -method=cert \
|
||||
-ca-cert=ca.pem -client-cert=cert.pem -client-key=key.pem
|
||||
-ca-cert=ca.pem -client-cert=cert.pem -client-key=key.pem \
|
||||
name=web
|
||||
```
|
||||
|
||||
### Via the API
|
||||
The endpoint for the login is `/login`. The client simply connects with their TLS
|
||||
certificate and when the login endpoint is hit, the auth backend will determine
|
||||
if there is a matching trusted certificate to authenticate the client.
|
||||
if there is a matching trusted certificate to authenticate the client. Optionally,
|
||||
you may specify a single certificate role to authenticate against.
|
||||
|
||||
```
|
||||
$ curl --cacert ca.pem --cert cert.pem --key key.pem \
|
||||
$ curl --cacert ca.pem --cert cert.pem --key key.pem -d name=web \
|
||||
$VAULT_ADDR/v1/auth/cert/login -XPOST
|
||||
```
|
||||
|
||||
|
@ -175,6 +182,7 @@ of the header should be "X-Vault-Token" and the value should be the token.
|
|||
"certificate": "-----BEGIN CERTIFICATE-----\nMIIEtzCCA5+.......ZRtAfQ6r\nwlW975rYa1ZqEdA=\n-----END CERTIFICATE-----",
|
||||
"display_name": "test",
|
||||
"policies": "",
|
||||
"allowed_names": "",
|
||||
"ttl": 2764800
|
||||
},
|
||||
"warnings": null,
|
||||
|
@ -245,6 +253,15 @@ of the header should be "X-Vault-Token" and the value should be the token.
|
|||
<span class="param-flags">required</span>
|
||||
The PEM-format CA certificate.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">allowed_names</span>
|
||||
<span class="param-flags">optional</span>
|
||||
Constrain the Common and Alternative Names in the client certificate
|
||||
with a [globbed pattern](https://github.com/ryanuber/go-glob/blob/master/README.md#example).
|
||||
Value is a comma-separated list of patterns.
|
||||
Authentication requires at least one Name matching at least one pattern.
|
||||
If not set, defaults to allowing all names.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">policies</span>
|
||||
<span class="param-flags">optional</span>
|
||||
|
@ -382,8 +399,8 @@ of the header should be "X-Vault-Token" and the value should be the token.
|
|||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Log in and fetch a token. If there is a valid chain to a CA configured in the backend,
|
||||
a token will be issued.
|
||||
Log in and fetch a token. If there is a valid chain to a CA configured in
|
||||
the backend and all role constraints are matched, a token will be issued.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
|
@ -394,7 +411,15 @@ of the header should be "X-Vault-Token" and the value should be the token.
|
|||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
None.
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">name</span>
|
||||
<span class="param-flags">optional</span>
|
||||
Authenticate against only the named certificate role, returning its
|
||||
policy list if successful. If not set, defaults to trying all
|
||||
certificate roles and returning any one that matches.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
|
|
|
@ -193,6 +193,6 @@ storage "consul" {
|
|||
```
|
||||
|
||||
[consul]: https://www.consul.io/ "Consul by HashiCorp"
|
||||
[consul-acl]: https://www.consul.io/docs/internals/acl.html "Consul ACLs"
|
||||
[consul-consistency]: https://www.consul.io/docs/agent/http.html#consistency-modes "Consul Consistency Modes"
|
||||
[consul-acl]: https://www.consul.io/docs/guides/acl.html "Consul ACLs"
|
||||
[consul-consistency]: https://www.consul.io/api/index.html#consistency-modes "Consul Consistency Modes"
|
||||
[consul-encryption]: https://www.consul.io/docs/agent/encryption.html "Consul Encryption"
|
||||
|
|
Loading…
Reference in New Issue