mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-03 03:58:01 +00:00
Merge branch 'master' into token-roles
This commit is contained in:
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,11 +1,34 @@
|
||||
## 0.5.2 (Unreleased)
|
||||
|
||||
IMPROVEMENTS:
|
||||
* core: Ignore leading `/` in policy paths [GH-1170]
|
||||
* core: Ignore leading `/` in mount paths [GH-1172]
|
||||
* command/server: The initial root token ID when running in `-dev` mode can
|
||||
now be specified via `-dev-root-token-id` or the environment variable
|
||||
`VAULT_DEV_ROOT_TOKEN_ID` [GH-1162]
|
||||
* command/server: The listen address when running in `-dev` mode can now be
|
||||
specified via `-dev-listen-address` or the environment variable
|
||||
`VAULT_DEV_LISTEN_ADDRESS` [GH-1169]
|
||||
* command/step-down: New `vault step-down` command and API endpoint to force
|
||||
the targeted node to give up active status, but without sealing. The node
|
||||
will wait ten seconds before attempting too grab the lock again. [GH-1146]
|
||||
* command/token-renew: Allow no token to be passed in; use `renew-self` in
|
||||
this case. Change the behavior for any token being passed in to use `renew`.
|
||||
[GH-1150]
|
||||
* credential/cert: Non-CA certificates can be used for authentication. They
|
||||
must be matched exactly (issuer and serial number) for authentication, and
|
||||
the certificate must carry the client authentication or 'any' extended usage
|
||||
attributes. [GH-1153]
|
||||
* secret/ssh: Added documentation for `ssh/config/zeroaddress` endpoint. [GH-1154]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* logical/cassandra: Apply hyphen/underscore replacement to the entire
|
||||
generated username, not just the UUID, in order to handle token display name
|
||||
hyphens [GH-1140]
|
||||
* physical/etcd: Output actual error when cluster sync fails [GH-1141]
|
||||
* logical/cassandra: Apply hyphen/underscore replacement to the entire
|
||||
generated username, not just the UUID, in order to handle token display name
|
||||
hyphens [GH-1140]
|
||||
* physical/etcd: Output actual error when cluster sync fails [GH-1141]
|
||||
* vault/expiration: Not letting the error responses from the backends to skip
|
||||
during renewals [GH-1176]
|
||||
|
||||
## 0.5.1 (February 25th, 2016)
|
||||
|
||||
|
||||
@@ -18,10 +18,6 @@ func (c *Sys) ListAuth() (map[string]*AuthMount, error) {
|
||||
}
|
||||
|
||||
func (c *Sys) EnableAuth(path, authType, desc string) error {
|
||||
if err := c.checkAuthPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := map[string]string{
|
||||
"type": authType,
|
||||
"description": desc,
|
||||
@@ -42,10 +38,6 @@ func (c *Sys) EnableAuth(path, authType, desc string) error {
|
||||
}
|
||||
|
||||
func (c *Sys) DisableAuth(path string) error {
|
||||
if err := c.checkAuthPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := c.c.NewRequest("DELETE", fmt.Sprintf("/v1/sys/auth/%s", path))
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err == nil {
|
||||
@@ -54,14 +46,6 @@ func (c *Sys) DisableAuth(path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Sys) checkAuthPath(path string) error {
|
||||
if path[0] == '/' {
|
||||
return fmt.Errorf("path must not start with /: %s", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Structures for the requests/resposne are all down here. They aren't
|
||||
// individually documentd because the map almost directly to the raw HTTP API
|
||||
// documentation. Please refer to that documentation for more details.
|
||||
|
||||
@@ -20,10 +20,6 @@ func (c *Sys) ListMounts() (map[string]*MountOutput, error) {
|
||||
}
|
||||
|
||||
func (c *Sys) Mount(path string, mountInfo *MountInput) error {
|
||||
if err := c.checkMountPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := structs.Map(mountInfo)
|
||||
|
||||
r := c.c.NewRequest("POST", fmt.Sprintf("/v1/sys/mounts/%s", path))
|
||||
@@ -41,10 +37,6 @@ func (c *Sys) Mount(path string, mountInfo *MountInput) error {
|
||||
}
|
||||
|
||||
func (c *Sys) Unmount(path string) error {
|
||||
if err := c.checkMountPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := c.c.NewRequest("DELETE", fmt.Sprintf("/v1/sys/mounts/%s", path))
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err == nil {
|
||||
@@ -54,13 +46,6 @@ func (c *Sys) Unmount(path string) error {
|
||||
}
|
||||
|
||||
func (c *Sys) Remount(from, to string) error {
|
||||
if err := c.checkMountPath(from); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkMountPath(to); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"from": from,
|
||||
"to": to,
|
||||
@@ -79,10 +64,6 @@ func (c *Sys) Remount(from, to string) error {
|
||||
}
|
||||
|
||||
func (c *Sys) TuneMount(path string, config MountConfigInput) error {
|
||||
if err := c.checkMountPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := structs.Map(config)
|
||||
r := c.c.NewRequest("POST", fmt.Sprintf("/v1/sys/mounts/%s/tune", path))
|
||||
if err := r.SetJSONBody(body); err != nil {
|
||||
@@ -97,10 +78,6 @@ func (c *Sys) TuneMount(path string, config MountConfigInput) error {
|
||||
}
|
||||
|
||||
func (c *Sys) MountConfig(path string) (*MountConfigOutput, error) {
|
||||
if err := c.checkMountPath(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := c.c.NewRequest("GET", fmt.Sprintf("/v1/sys/mounts/%s/tune", path))
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
@@ -113,14 +90,6 @@ func (c *Sys) MountConfig(path string) (*MountConfigOutput, error) {
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (c *Sys) checkMountPath(path string) error {
|
||||
if path[0] == '/' {
|
||||
return fmt.Errorf("path must not start with /: %s", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MountInput struct {
|
||||
Type string `json:"type" structs:"type"`
|
||||
Description string `json:"description" structs:"description"`
|
||||
|
||||
10
api/sys_stepdown.go
Normal file
10
api/sys_stepdown.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package api
|
||||
|
||||
func (c *Sys) StepDown() error {
|
||||
r := c.c.NewRequest("PUT", "/v1/sys/step-down")
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -27,6 +27,33 @@ func testFactory(t *testing.T) logical.Backend {
|
||||
return b
|
||||
}
|
||||
|
||||
// Test the certificates being registered to the backend
|
||||
func TestBackend_CertWrites(t *testing.T) {
|
||||
// CA cert
|
||||
ca1, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
// Non CA Cert
|
||||
ca2, err := ioutil.ReadFile("test-fixtures/keys/cert.pem")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
// Non CA cert without TLS web client authentication
|
||||
ca3, err := ioutil.ReadFile("test-fixtures/noclientauthcert.pem")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: testFactory(t),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepCert(t, "web", ca1, "foo", false),
|
||||
testAccStepCert(t, "web", ca2, "foo", false),
|
||||
testAccStepCert(t, "web", ca3, "foo", true),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Test a client trusted by a CA
|
||||
func TestBackend_basic_CA(t *testing.T) {
|
||||
connState := testConnState(t, "test-fixtures/keys/cert.pem",
|
||||
@@ -38,7 +65,7 @@ func TestBackend_basic_CA(t *testing.T) {
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: testFactory(t),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepCert(t, "web", ca, "foo"),
|
||||
testAccStepCert(t, "web", ca, "foo", false),
|
||||
testAccStepLogin(t, connState),
|
||||
testAccStepCertLease(t, "web", ca, "foo"),
|
||||
testAccStepCertTTL(t, "web", ca, "foo"),
|
||||
@@ -86,7 +113,7 @@ func TestBackend_basic_singleCert(t *testing.T) {
|
||||
logicaltest.Test(t, logicaltest.TestCase{
|
||||
Backend: testFactory(t),
|
||||
Steps: []logicaltest.TestStep{
|
||||
testAccStepCert(t, "web", ca, "foo"),
|
||||
testAccStepCert(t, "web", ca, "foo", false),
|
||||
testAccStepLogin(t, connState),
|
||||
},
|
||||
})
|
||||
@@ -196,16 +223,23 @@ func testAccStepLoginInvalid(t *testing.T, connState tls.ConnectionState) logica
|
||||
}
|
||||
|
||||
func testAccStepCert(
|
||||
t *testing.T, name string, cert []byte, policies string) logicaltest.TestStep {
|
||||
t *testing.T, name string, cert []byte, policies 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,
|
||||
},
|
||||
Check: func(resp *logical.Response) error {
|
||||
if resp == nil && expectError {
|
||||
return fmt.Errorf("expected error but received nil")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -51,7 +52,7 @@ Defaults to system/backend default TTL time.`,
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.DeleteOperation: b.pathCertDelete,
|
||||
logical.ReadOperation: b.pathCertRead,
|
||||
logical.UpdateOperation: b.pathCertWrite,
|
||||
logical.UpdateOperation: b.pathCertWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathCertHelpSyn,
|
||||
@@ -132,6 +133,20 @@ func (b *backend) pathCertWrite(
|
||||
return logical.ErrorResponse("failed to parse certificate"), nil
|
||||
}
|
||||
|
||||
// If the certificate is not a CA cert, then ensure that x509.ExtKeyUsageClientAuth is set
|
||||
if !parsed[0].IsCA && parsed[0].ExtKeyUsage != nil {
|
||||
var clientAuth bool
|
||||
for _, usage := range parsed[0].ExtKeyUsage {
|
||||
if usage == x509.ExtKeyUsageClientAuth || usage == x509.ExtKeyUsageAny {
|
||||
clientAuth = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !clientAuth {
|
||||
return logical.ErrorResponse("non-CA certificates should have TLS client authentication set as an extended key usage"), nil
|
||||
}
|
||||
}
|
||||
|
||||
certEntry := &CertEntry{
|
||||
Name: name,
|
||||
Certificate: certificate,
|
||||
@@ -140,7 +155,6 @@ func (b *backend) pathCertWrite(
|
||||
}
|
||||
|
||||
// Parse the lease duration or default to backend/system default
|
||||
var err error
|
||||
maxTTL := b.System().MaxLeaseTTL()
|
||||
ttl := time.Duration(d.Get("ttl").(int)) * time.Second
|
||||
if ttl == time.Duration(0) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
@@ -134,7 +135,16 @@ func (b *backend) verifyCredentials(req *logical.Request) (*ParsedCert, *logical
|
||||
connState := req.Connection.ConnState
|
||||
|
||||
// Load the trusted certificates
|
||||
roots, trusted := b.loadTrustedCerts(req.Storage)
|
||||
roots, trusted, trustedNonCAs := b.loadTrustedCerts(req.Storage)
|
||||
|
||||
// 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 {
|
||||
return policy, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the connection state is trusted
|
||||
trustedChains, err := validateConnState(roots, connState)
|
||||
@@ -158,6 +168,18 @@ func (b *backend) verifyCredentials(req *logical.Request) (*ParsedCert, *logical
|
||||
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 {
|
||||
@@ -177,7 +199,7 @@ func (b *backend) matchPolicy(chains [][]*x509.Certificate, trusted []*ParsedCer
|
||||
}
|
||||
|
||||
// loadTrustedCerts is used to load all the trusted certificates from the backend
|
||||
func (b *backend) loadTrustedCerts(store logical.Storage) (pool *x509.CertPool, trusted []*ParsedCert) {
|
||||
func (b *backend) loadTrustedCerts(store logical.Storage) (pool *x509.CertPool, trusted []*ParsedCert, trustedNonCAs []*ParsedCert) {
|
||||
pool = x509.NewCertPool()
|
||||
names, err := store.List("cert/")
|
||||
if err != nil {
|
||||
@@ -195,15 +217,22 @@ func (b *backend) loadTrustedCerts(store logical.Storage) (pool *x509.CertPool,
|
||||
b.Logger().Printf("[ERR] cert: failed to parse certificate for '%s'", name)
|
||||
continue
|
||||
}
|
||||
for _, p := range parsed {
|
||||
pool.AddCert(p)
|
||||
}
|
||||
if !parsed[0].IsCA {
|
||||
trustedNonCAs = append(trustedNonCAs, &ParsedCert{
|
||||
Entry: entry,
|
||||
Certificates: parsed,
|
||||
})
|
||||
} else {
|
||||
for _, p := range parsed {
|
||||
pool.AddCert(p)
|
||||
}
|
||||
|
||||
// Create a ParsedCert entry
|
||||
trusted = append(trusted, &ParsedCert{
|
||||
Entry: entry,
|
||||
Certificates: parsed,
|
||||
})
|
||||
// Create a ParsedCert entry
|
||||
trusted = append(trusted, &ParsedCert{
|
||||
Entry: entry,
|
||||
Certificates: parsed,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -257,6 +286,7 @@ func validateConnState(roots *x509.CertPool, cs *tls.ConnectionState) ([][]*x509
|
||||
Intermediates: x509.NewCertPool(),
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
certs := cs.PeerCertificates
|
||||
if len(certs) == 0 {
|
||||
return nil, nil
|
||||
|
||||
19
builtin/credential/cert/test-fixtures/noclientauthcert.pem
Normal file
19
builtin/credential/cert/test-fixtures/noclientauthcert.pem
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDGTCCAgGgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBxMQowCAYDVQQDFAEqMQsw
|
||||
CQYDVQQIEwJHQTELMAkGA1UEBhMCVVMxJTAjBgkqhkiG9w0BCQEWFnZpc2hhbG5h
|
||||
eWFrdkBnbWFpbC5jb20xEjAQBgNVBAoTCUhhc2hpQ29ycDEOMAwGA1UECxMFVmF1
|
||||
bHQwHhcNMTYwMjI5MjE0NjE2WhcNMjEwMjI3MjE0NjE2WjBxMQowCAYDVQQDFAEq
|
||||
MQswCQYDVQQIEwJHQTELMAkGA1UEBhMCVVMxJTAjBgkqhkiG9w0BCQEWFnZpc2hh
|
||||
bG5heWFrdkBnbWFpbC5jb20xEjAQBgNVBAoTCUhhc2hpQ29ycDEOMAwGA1UECxMF
|
||||
VmF1bHQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMfRkLfIGHt1r2jjnV0N
|
||||
LqRCu3oB+J1dqpM03vQt3qzIiqtuQuIA2ba7TJm2HwU3W3+rtfFcS+hkBR/LZM+u
|
||||
cBPB+9b9+7i08vHjgy2P3QH/Ebxa8j1v7JtRMT2qyxWK8NlT/+wZSH82Cr812aS/
|
||||
zNT56FbBo2UAtzpqeC4eiv6NAgMBAAGjQDA+MAkGA1UdEwQCMAAwCwYDVR0PBAQD
|
||||
AgXgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZI
|
||||
hvcNAQEFBQADggEBAG2mUwsZ6+R8qqyNjzMk7mgpsRZv9TEl6c1IiQdyjaCOPaYH
|
||||
vtZpLX20um36cxrLuOUtZLllG/VJEhRZW5mXWxuOk4QunWMBXQioCDJG1ktcZAcQ
|
||||
QqYv9Dzy2G9lZHjLztEac37T75RXW7OEeQREgwP11c8sQYiS9jf+7ITYL7nXjoKq
|
||||
gEuH0h86BOH2O/BxgMelt9O0YCkvkLLHnE27xuNelRRZcBLSuE1GxdUi32MDJ+ff
|
||||
25GUNM0zzOEaJAFE/USUBEdQqN1gvJidNXkAiMtIK7T8omQZONRaD2ZnSW8y2krh
|
||||
eUg+rKis9RinqFlahLPfI5BlyQsNMEnsD07Q85E=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -76,7 +76,7 @@ func (b *backend) pathLoginRenew(
|
||||
|
||||
sort.Strings(policies)
|
||||
if strings.Join(policies, ",") != prevpolicies {
|
||||
return logical.ErrorResponse("policies have changed, revoking login"), nil
|
||||
return logical.ErrorResponse("policies have changed, not renewing"), nil
|
||||
}
|
||||
|
||||
return framework.LeaseExtend(0, 0, b.System())(req, d)
|
||||
|
||||
@@ -224,6 +224,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
|
||||
}, nil
|
||||
},
|
||||
|
||||
"step-down": func() (cli.Command, error) {
|
||||
return &command.StepDownCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"mount": func() (cli.Command, error) {
|
||||
return &command.MountCommand{
|
||||
Meta: meta,
|
||||
|
||||
@@ -53,7 +53,7 @@ Usage: vault policy-delete [options] name
|
||||
|
||||
Delete a policy with the given name.
|
||||
|
||||
One the policy is deleted, all users associated with the policy will
|
||||
Once the policy is deleted, all users associated with the policy will
|
||||
be affected immediately. When a user is associated with a policy that
|
||||
doesn't exist, it is identical to not being associated with that policy.
|
||||
|
||||
|
||||
@@ -41,9 +41,11 @@ type ServerCommand struct {
|
||||
func (c *ServerCommand) Run(args []string) int {
|
||||
var dev, verifyOnly bool
|
||||
var configPath []string
|
||||
var logLevel string
|
||||
var logLevel, devRootTokenID, devListenAddress string
|
||||
flags := c.Meta.FlagSet("server", FlagSetDefault)
|
||||
flags.BoolVar(&dev, "dev", false, "")
|
||||
flags.StringVar(&devRootTokenID, "dev-root-token-id", "", "")
|
||||
flags.StringVar(&devListenAddress, "dev-listen-address", "", "")
|
||||
flags.StringVar(&logLevel, "log-level", "info", "")
|
||||
flags.BoolVar(&verifyOnly, "verify-only", false, "")
|
||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
@@ -52,17 +54,39 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
if os.Getenv("VAULT_DEV_ROOT_TOKEN_ID") != "" {
|
||||
devRootTokenID = os.Getenv("VAULT_DEV_ROOT_TOKEN_ID")
|
||||
}
|
||||
|
||||
if os.Getenv("VAULT_DEV_LISTEN_ADDRESS") != "" {
|
||||
devListenAddress = os.Getenv("VAULT_DEV_LISTEN_ADDRESS")
|
||||
}
|
||||
|
||||
// Validation
|
||||
if !dev && len(configPath) == 0 {
|
||||
c.Ui.Error("At least one config path must be specified with -config")
|
||||
flags.Usage()
|
||||
return 1
|
||||
if !dev {
|
||||
switch {
|
||||
case len(configPath) == 0:
|
||||
c.Ui.Error("At least one config path must be specified with -config")
|
||||
flags.Usage()
|
||||
return 1
|
||||
case devRootTokenID != "":
|
||||
c.Ui.Error("Root token ID can only be specified with -dev")
|
||||
flags.Usage()
|
||||
return 1
|
||||
case devListenAddress != "":
|
||||
c.Ui.Error("Development address can only be specified with -dev")
|
||||
flags.Usage()
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Load the configuration
|
||||
var config *server.Config
|
||||
if dev {
|
||||
config = server.DevConfig()
|
||||
if devListenAddress != "" {
|
||||
config.Listeners[0].Config["address"] = devListenAddress
|
||||
}
|
||||
}
|
||||
for _, path := range configPath {
|
||||
current, err := server.LoadConfig(path)
|
||||
@@ -193,7 +217,7 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
|
||||
// If we're in dev mode, then initialize the core
|
||||
if dev {
|
||||
init, err := c.enableDev(core)
|
||||
init, err := c.enableDev(core, devRootTokenID)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error initializing dev mode: %s", err))
|
||||
@@ -215,7 +239,7 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
"immediately begin using the Vault CLI.\n\n"+
|
||||
"The only step you need to take is to set the following\n"+
|
||||
"environment variables:\n\n"+
|
||||
" "+export+" VAULT_ADDR="+quote+"http://127.0.0.1:8200"+quote+"\n\n"+
|
||||
" "+export+" VAULT_ADDR="+quote+"http://"+config.Listeners[0].Config["address"]+quote+"\n\n"+
|
||||
"The unseal key and root token are reproduced below in case you\n"+
|
||||
"want to seal/unseal the Vault or play with authentication.\n\n"+
|
||||
"Unseal Key: %s\nRoot Token: %s\n",
|
||||
@@ -319,7 +343,7 @@ func (c *ServerCommand) Run(args []string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *ServerCommand) enableDev(core *vault.Core) (*vault.InitResult, error) {
|
||||
func (c *ServerCommand) enableDev(core *vault.Core, rootTokenID string) (*vault.InitResult, error) {
|
||||
// Initialize it with a basic single key
|
||||
init, err := core.Initialize(&vault.SealConfig{
|
||||
SecretShares: 1,
|
||||
@@ -342,6 +366,39 @@ func (c *ServerCommand) enableDev(core *vault.Core) (*vault.InitResult, error) {
|
||||
return nil, fmt.Errorf("failed to unseal Vault for dev mode")
|
||||
}
|
||||
|
||||
if rootTokenID != "" {
|
||||
req := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
ClientToken: init.RootToken,
|
||||
Path: "auth/token/create",
|
||||
Data: map[string]interface{}{
|
||||
"id": rootTokenID,
|
||||
"policies": []string{"root"},
|
||||
"no_parent": true,
|
||||
"no_default_policy": true,
|
||||
},
|
||||
}
|
||||
resp, err := core.HandleRequest(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create root token with ID %s: %s", rootTokenID, err)
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, fmt.Errorf("nil response when creating root token with ID %s", rootTokenID)
|
||||
}
|
||||
if resp.Auth == nil {
|
||||
return nil, fmt.Errorf("nil auth when creating root token with ID %s", rootTokenID)
|
||||
}
|
||||
|
||||
init.RootToken = resp.Auth.ClientToken
|
||||
|
||||
req.Path = "auth/token/revoke-self"
|
||||
req.Data = nil
|
||||
resp, err = core.HandleRequest(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to revoke initial root token: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the token
|
||||
tokenHelper, err := c.TokenHelper()
|
||||
if err != nil {
|
||||
@@ -495,18 +552,28 @@ Usage: vault server [options]
|
||||
|
||||
General Options:
|
||||
|
||||
-config=<path> Path to the configuration file or directory. This can be
|
||||
specified multiple times. If it is a directory, all
|
||||
files with a ".hcl" or ".json" suffix will be loaded.
|
||||
-config=<path> Path to the configuration file or directory. This can
|
||||
be specified multiple times. If it is a directory,
|
||||
all files with a ".hcl" or ".json" suffix will be
|
||||
loaded.
|
||||
|
||||
-dev Enables Dev mode. In this mode, Vault is completely
|
||||
in-memory and unsealed. Do not run the Dev server in
|
||||
production!
|
||||
-dev Enables Dev mode. In this mode, Vault is completely
|
||||
in-memory and unsealed. Do not run the Dev server in
|
||||
production!
|
||||
|
||||
-log-level=info Log verbosity. Defaults to "info", will be outputted
|
||||
to stderr. Supported values: "trace", "debug", "info",
|
||||
"warn", "err"
|
||||
-dev-root-token-id="" If set, the root token returned in Dev mode will have
|
||||
the given ID. This *only* has an effect when running
|
||||
in Dev mode. Can also be specified with the
|
||||
VAULT_DEV_ROOT_TOKEN_ID environment variable.
|
||||
|
||||
-dev-listen-address="" If set, this overrides the normal Dev mode listen
|
||||
address of "127.0.0.1:8200". Can also be specified
|
||||
with the VAULT_DEV_LISTEN_ADDRESS environment
|
||||
variable.
|
||||
|
||||
-log-level=info Log verbosity. Defaults to "info", will be output to
|
||||
stderr. Supported values: "trace", "debug", "info",
|
||||
"warn", "err"
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ func DevConfig() *Config {
|
||||
&Listener{
|
||||
Type: "tcp",
|
||||
Config: map[string]string{
|
||||
"address": "127.0.0.1:8200",
|
||||
"tls_disable": "1",
|
||||
},
|
||||
},
|
||||
|
||||
54
command/step-down.go
Normal file
54
command/step-down.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StepDownCommand is a Command that seals the vault.
|
||||
type StepDownCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *StepDownCommand) Run(args []string) int {
|
||||
flags := c.Meta.FlagSet("step-down", FlagSetDefault)
|
||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error initializing client: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
if err := client.Sys().StepDown(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error stepping down: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *StepDownCommand) Synopsis() string {
|
||||
return "Force the Vault node to give up active duty"
|
||||
}
|
||||
|
||||
func (c *StepDownCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault step-down [options]
|
||||
|
||||
Force the Vault node to step down from active duty.
|
||||
|
||||
This causes the indicated node to give up active status. Note that while the
|
||||
affected node will have a short delay before attempting to grab the lock
|
||||
again, if no other node grabs the lock beforehand, it is possible for the
|
||||
same node to re-grab the lock and become active again.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage()
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
)
|
||||
@@ -14,32 +14,41 @@ type TokenRenewCommand struct {
|
||||
}
|
||||
|
||||
func (c *TokenRenewCommand) Run(args []string) int {
|
||||
var format string
|
||||
var format, increment string
|
||||
flags := c.Meta.FlagSet("token-renew", FlagSetDefault)
|
||||
flags.StringVar(&format, "format", "table", "")
|
||||
flags.StringVar(&increment, "increment", "", "")
|
||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
args = flags.Args()
|
||||
if len(args) < 1 {
|
||||
if len(args) > 2 {
|
||||
flags.Usage()
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"\ntoken-renew expects at least one argument"))
|
||||
"\ntoken-renew expects at most two arguments"))
|
||||
return 1
|
||||
}
|
||||
|
||||
var increment int
|
||||
token := args[0]
|
||||
if len(args) > 1 {
|
||||
value, err := strconv.ParseInt(args[1], 10, 0)
|
||||
var token string
|
||||
if len(args) > 0 {
|
||||
token = args[0]
|
||||
}
|
||||
|
||||
var inc int
|
||||
// If both are specified prefer the argument
|
||||
if len(args) == 2 {
|
||||
increment = args[1]
|
||||
}
|
||||
if increment != "" {
|
||||
dur, err := time.ParseDuration(increment)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Invalid increment: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
increment = int(value)
|
||||
inc = int(dur / time.Second)
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
@@ -52,10 +61,10 @@ func (c *TokenRenewCommand) Run(args []string) int {
|
||||
// If the given token is the same as the client's, use renew-self instead
|
||||
// as this is far more likely to be allowed via policy
|
||||
var secret *api.Secret
|
||||
if client.Token() == token {
|
||||
secret, err = client.Auth().Token().RenewSelf(increment)
|
||||
if token == "" {
|
||||
secret, err = client.Auth().Token().RenewSelf(inc)
|
||||
} else {
|
||||
secret, err = client.Auth().Token().Renew(token, increment)
|
||||
secret, err = client.Auth().Token().Renew(token, inc)
|
||||
}
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
@@ -72,17 +81,20 @@ func (c *TokenRenewCommand) Synopsis() string {
|
||||
|
||||
func (c *TokenRenewCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault token-renew [options] token [increment]
|
||||
Usage: vault token-renew [options] [token] [increment]
|
||||
|
||||
Renew an auth token, extending the amount of time it can be used.
|
||||
Token is renewable only if there is a lease associated with it.
|
||||
Renew an auth token, extending the amount of time it can be used. If a token
|
||||
is given to the command, '/auth/token/renew' will be called with the given
|
||||
token; otherwise, '/auth/token/renew-self' will be called with the client
|
||||
token.
|
||||
|
||||
This command is similar to "renew", but "renew" is only for lease IDs.
|
||||
This command is only for tokens.
|
||||
This command is similar to "renew", but "renew" is only for leases; this
|
||||
command is only for tokens.
|
||||
|
||||
An optional increment can be given to request a certain number of
|
||||
seconds to increment the lease. This request is advisory; Vault may not
|
||||
adhere to it at all.
|
||||
An optional increment can be given to request a certain number of seconds to
|
||||
increment the lease. This request is advisory; Vault may not adhere to it at
|
||||
all. If a token is being passed in on the command line, the increment can as
|
||||
well; otherwise it must be passed in via the '-increment' flag.
|
||||
|
||||
General Options:
|
||||
|
||||
@@ -90,6 +102,11 @@ General Options:
|
||||
|
||||
Token Renew Options:
|
||||
|
||||
-increment=3600 The desired increment. If not supplied, Vault will
|
||||
use the default TTL. If supplied, it may still be
|
||||
ignored. This can be submitted as an integer number
|
||||
of seconds or a string duration (e.g. "72h").
|
||||
|
||||
-format=table The format for output. By default it is a whitespace-
|
||||
delimited table. This can also be json or yaml.
|
||||
|
||||
|
||||
@@ -41,9 +41,136 @@ func TestTokenRenew(t *testing.T) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Verify it worked
|
||||
// Renew, passing in the token
|
||||
args = append(args, resp.Auth.ClientToken)
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenRenewWithIncrement(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &TokenRenewCommand{
|
||||
Meta: Meta{
|
||||
ClientToken: token,
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-address", addr,
|
||||
}
|
||||
|
||||
// Run it once for client
|
||||
c.Run(args)
|
||||
|
||||
// Create a token
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
resp, err := client.Auth().Token().Create(&api.TokenCreateRequest{
|
||||
Lease: "1h",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Renew, passing in the token
|
||||
args = append(args, resp.Auth.ClientToken)
|
||||
args = append(args, "72h")
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenRenewSelf(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &TokenRenewCommand{
|
||||
Meta: Meta{
|
||||
ClientToken: token,
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-address", addr,
|
||||
}
|
||||
|
||||
// Run it once for client
|
||||
c.Run(args)
|
||||
|
||||
// Create a token
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
resp, err := client.Auth().Token().Create(&api.TokenCreateRequest{
|
||||
Lease: "1h",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if resp.Auth.ClientToken == "" {
|
||||
t.Fatal("returned client token is empty")
|
||||
}
|
||||
|
||||
c.Meta.ClientToken = resp.Auth.ClientToken
|
||||
|
||||
// Renew using the self endpoint
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenRenewSelfWithIncrement(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &TokenRenewCommand{
|
||||
Meta: Meta{
|
||||
ClientToken: token,
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-address", addr,
|
||||
}
|
||||
|
||||
// Run it once for client
|
||||
c.Run(args)
|
||||
|
||||
// Create a token
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
resp, err := client.Auth().Token().Create(&api.TokenCreateRequest{
|
||||
Lease: "1h",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if resp.Auth.ClientToken == "" {
|
||||
t.Fatal("returned client token is empty")
|
||||
}
|
||||
|
||||
c.Meta.ClientToken = resp.Auth.ClientToken
|
||||
|
||||
args = append(args, "-increment=72h")
|
||||
// Renew using the self endpoint
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,29 +23,16 @@ func Handler(core *vault.Core) http.Handler {
|
||||
mux.Handle("/v1/sys/init", handleSysInit(core))
|
||||
mux.Handle("/v1/sys/seal-status", handleSysSealStatus(core))
|
||||
mux.Handle("/v1/sys/seal", handleSysSeal(core))
|
||||
mux.Handle("/v1/sys/step-down", handleSysStepDown(core))
|
||||
mux.Handle("/v1/sys/unseal", handleSysUnseal(core))
|
||||
mux.Handle("/v1/sys/mounts", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/mounts/", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/remount", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/policy", handleSysListPolicies(core))
|
||||
mux.Handle("/v1/sys/policy/", handleSysPolicy(core))
|
||||
mux.Handle("/v1/sys/renew/", handleLogical(core, false))
|
||||
mux.Handle("/v1/sys/revoke/", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/revoke-prefix/", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/auth", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/auth/", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/audit-hash/", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/audit", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/audit/", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/leader", handleSysLeader(core))
|
||||
mux.Handle("/v1/sys/health", handleSysHealth(core))
|
||||
mux.Handle("/v1/sys/rotate", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/key-status", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/generate-root/attempt", handleSysGenerateRootAttempt(core))
|
||||
mux.Handle("/v1/sys/generate-root/update", handleSysGenerateRootUpdate(core))
|
||||
mux.Handle("/v1/sys/rekey/init", handleSysRekeyInit(core))
|
||||
mux.Handle("/v1/sys/rekey/backup", proxySysRequest(core))
|
||||
mux.Handle("/v1/sys/rekey/update", handleSysRekeyUpdate(core))
|
||||
mux.Handle("/v1/sys/", handleLogical(core, true))
|
||||
mux.Handle("/v1/", handleLogical(core, false))
|
||||
|
||||
// Wrap the handler in another handler to trigger all help paths.
|
||||
@@ -214,10 +201,6 @@ func respondOk(w http.ResponseWriter, body interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func proxySysRequest(core *vault.Core) http.Handler {
|
||||
return handleLogical(core, true)
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
|
||||
@@ -85,6 +85,10 @@ func TestLogical_StandbyRedirect(t *testing.T) {
|
||||
t.Fatalf("unseal err: %s", err)
|
||||
}
|
||||
|
||||
// Attempt to fix raciness in this test by giving the first core a chance
|
||||
// to grab the lock
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Create a second HA Vault
|
||||
conf2 := &vault.CoreConfig{
|
||||
Physical: inmha,
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func handleSysListPolicies(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
respondError(w, http.StatusMethodNotAllowed, nil)
|
||||
return
|
||||
}
|
||||
|
||||
resp, ok := request(core, w, r, requestAuth(r, &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "sys/policy",
|
||||
Connection: getConnection(r),
|
||||
}))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var policies []string
|
||||
policiesRaw, ok := resp.Data["keys"]
|
||||
if ok {
|
||||
policies = policiesRaw.([]string)
|
||||
}
|
||||
|
||||
respondOk(w, &listPolicyResponse{Policies: policies})
|
||||
})
|
||||
}
|
||||
|
||||
func handleSysPolicy(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
handleSysReadPolicy(core, w, r)
|
||||
case "PUT":
|
||||
fallthrough
|
||||
case "POST":
|
||||
handleSysWritePolicy(core, w, r)
|
||||
case "DELETE":
|
||||
handleSysDeletePolicy(core, w, r)
|
||||
default:
|
||||
respondError(w, http.StatusMethodNotAllowed, nil)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func handleSysDeletePolicy(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
// Determine the path...
|
||||
prefix := "/v1/sys/policy/"
|
||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||
respondError(w, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
path := r.URL.Path[len(prefix):]
|
||||
if path == "" {
|
||||
respondError(w, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := request(core, w, r, requestAuth(r, &logical.Request{
|
||||
Operation: logical.DeleteOperation,
|
||||
Path: "sys/policy/" + path,
|
||||
Connection: getConnection(r),
|
||||
}))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
respondOk(w, nil)
|
||||
}
|
||||
|
||||
func handleSysReadPolicy(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
// Determine the path...
|
||||
prefix := "/v1/sys/policy/"
|
||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||
respondError(w, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
path := r.URL.Path[len(prefix):]
|
||||
if path == "" {
|
||||
respondError(w, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
resp, ok := request(core, w, r, requestAuth(r, &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "sys/policy/" + path,
|
||||
Connection: getConnection(r),
|
||||
}))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if resp == nil {
|
||||
respondError(w, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
respondOk(w, resp.Data)
|
||||
}
|
||||
|
||||
func handleSysWritePolicy(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
// Determine the path...
|
||||
prefix := "/v1/sys/policy/"
|
||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||
respondError(w, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
path := r.URL.Path[len(prefix):]
|
||||
if path == "" {
|
||||
respondError(w, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the request if we can
|
||||
var req writePolicyRequest
|
||||
if err := parseRequest(r, &req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, ok := request(core, w, r, requestAuth(r, &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "sys/policy/" + path,
|
||||
Connection: getConnection(r),
|
||||
Data: map[string]interface{}{
|
||||
"rules": req.Rules,
|
||||
},
|
||||
}))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
respondOk(w, nil)
|
||||
}
|
||||
|
||||
type listPolicyResponse struct {
|
||||
Policies []string `json:"policies"`
|
||||
}
|
||||
|
||||
type writePolicyRequest struct {
|
||||
Rules string `json:"rules"`
|
||||
}
|
||||
@@ -18,11 +18,12 @@ func TestSysPolicies(t *testing.T) {
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"policies": []interface{}{"default", "root"},
|
||||
"keys": []interface{}{"default", "root"},
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ func TestSysReadPolicy(t *testing.T) {
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,11 +63,12 @@ func TestSysWritePolicy(t *testing.T) {
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"policies": []interface{}{"default", "foo", "root"},
|
||||
"keys": []interface{}{"default", "foo", "root"},
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,10 +91,11 @@ func TestSysDeletePolicy(t *testing.T) {
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"policies": []interface{}{"default", "root"},
|
||||
"keys": []interface{}{"default", "root"},
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,29 @@ func handleSysSeal(core *vault.Core) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func handleSysStepDown(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "PUT":
|
||||
case "POST":
|
||||
default:
|
||||
respondError(w, http.StatusMethodNotAllowed, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the auth for the request so we can access the token directly
|
||||
req := requestAuth(r, &logical.Request{})
|
||||
|
||||
// Seal with the token above
|
||||
if err := core.StepDown(req.ClientToken); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
respondOk(w, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func handleSysUnseal(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
|
||||
@@ -304,3 +304,13 @@ func TestSysSeal_Permissions(t *testing.T) {
|
||||
httpResp = testHttpPut(t, "child", addr+"/v1/sys/seal", nil)
|
||||
testResponseStatus(t, httpResp, 204)
|
||||
}
|
||||
|
||||
func TestSysStepDown(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/step-down", nil)
|
||||
testResponseStatus(t, resp, 204)
|
||||
}
|
||||
|
||||
@@ -63,8 +63,7 @@ func NewACL(policies []*Policy) (*ACL, error) {
|
||||
|
||||
default:
|
||||
// Insert the capabilities in this new policy into the existing
|
||||
// value; since it's a pointer we can just modify the
|
||||
// underlying data
|
||||
// value
|
||||
tree.Insert(pc.Prefix, existing|pc.CapabilitiesBitmap)
|
||||
}
|
||||
}
|
||||
|
||||
110
vault/core.go
110
vault/core.go
@@ -64,6 +64,10 @@ const (
|
||||
// leaderPrefixCleanDelay is how long to wait between deletions
|
||||
// of orphaned leader keys, to prevent slamming the backend.
|
||||
leaderPrefixCleanDelay = 200 * time.Millisecond
|
||||
|
||||
// manualStepDownSleepPeriod is how long to sleep after a user-initiated
|
||||
// step down of the active node, to prevent instantly regrabbing the lock
|
||||
manualStepDownSleepPeriod = 10 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -206,9 +210,10 @@ type Core struct {
|
||||
stateLock sync.RWMutex
|
||||
sealed bool
|
||||
|
||||
standby bool
|
||||
standbyDoneCh chan struct{}
|
||||
standbyStopCh chan struct{}
|
||||
standby bool
|
||||
standbyDoneCh chan struct{}
|
||||
standbyStopCh chan struct{}
|
||||
manualStepDownCh chan struct{}
|
||||
|
||||
// unlockParts has the keys provided to Unseal until
|
||||
// the threshold number of parts is available.
|
||||
@@ -1132,7 +1137,8 @@ func (c *Core) Unseal(key []byte) (bool, error) {
|
||||
// Go to standby mode, wait until we are active to unseal
|
||||
c.standbyDoneCh = make(chan struct{})
|
||||
c.standbyStopCh = make(chan struct{})
|
||||
go c.runStandby(c.standbyDoneCh, c.standbyStopCh)
|
||||
c.manualStepDownCh = make(chan struct{})
|
||||
go c.runStandby(c.standbyDoneCh, c.standbyStopCh, c.manualStepDownCh)
|
||||
}
|
||||
|
||||
// Success!
|
||||
@@ -1144,6 +1150,7 @@ func (c *Core) Unseal(key []byte) (bool, error) {
|
||||
// be unsealed again to perform any further operations.
|
||||
func (c *Core) Seal(token string) (retErr error) {
|
||||
defer metrics.MeasureSince([]string{"core", "seal"}, time.Now())
|
||||
|
||||
c.stateLock.Lock()
|
||||
defer c.stateLock.Unlock()
|
||||
if c.sealed {
|
||||
@@ -1156,15 +1163,8 @@ func (c *Core) Seal(token string) (retErr error) {
|
||||
Path: "sys/seal",
|
||||
ClientToken: token,
|
||||
}
|
||||
acl, te, err := c.fetchACLandTokenEntry(req)
|
||||
|
||||
// Attempt to use the token (decrement num_uses)
|
||||
if te != nil {
|
||||
if err := c.tokenStore.UseToken(te); err != nil {
|
||||
c.logger.Printf("[ERR] core: failed to use token: %v", err)
|
||||
retErr = ErrInternalError
|
||||
}
|
||||
}
|
||||
acl, te, err := c.fetchACLandTokenEntry(req)
|
||||
if err != nil {
|
||||
// Since there is no token store in standby nodes, sealing cannot
|
||||
// be done. Ideally, the request has to be forwarded to leader node
|
||||
@@ -1172,11 +1172,20 @@ func (c *Core) Seal(token string) (retErr error) {
|
||||
// just returning with an error and recommending a vault restart, which
|
||||
// essentially does the same thing.
|
||||
if c.standby {
|
||||
c.logger.Printf("[ERR] core: vault cannot be sealed when in standby mode; please restart instead")
|
||||
return errors.New("vault cannot be sealed when in standby mode; please restart instead")
|
||||
c.logger.Printf("[ERR] core: vault cannot seal when in standby mode; please restart instead")
|
||||
return errors.New("vault cannot seal when in standby mode; please restart instead")
|
||||
}
|
||||
return err
|
||||
}
|
||||
// Attempt to use the token (decrement num_uses)
|
||||
// If we can't, we still continue attempting the seal, so long as the token
|
||||
// has appropriate permissions
|
||||
if te != nil {
|
||||
if err := c.tokenStore.UseToken(te); err != nil {
|
||||
c.logger.Printf("[ERR] core: failed to use token: %v", err)
|
||||
retErr = ErrInternalError
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that this operation is allowed
|
||||
allowed, rootPrivs := acl.AllowOperation(req.Operation, req.Path)
|
||||
@@ -1189,7 +1198,7 @@ func (c *Core) Seal(token string) (retErr error) {
|
||||
return logical.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Seal the Vault
|
||||
//Seal the Vault
|
||||
err = c.sealInternal()
|
||||
if err == nil && retErr == ErrInternalError {
|
||||
c.logger.Printf("[ERR] core: core is successfully sealed but another error occurred during the operation")
|
||||
@@ -1200,9 +1209,60 @@ func (c *Core) Seal(token string) (retErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
// sealInternal is an internal method used to seal the vault.
|
||||
// It does not do any authorization checking. The stateLock must
|
||||
// be held prior to calling.
|
||||
// StepDown is used to step down from leadership
|
||||
func (c *Core) StepDown(token string) error {
|
||||
defer metrics.MeasureSince([]string{"core", "step_down"}, time.Now())
|
||||
|
||||
c.stateLock.Lock()
|
||||
defer c.stateLock.Unlock()
|
||||
if c.sealed {
|
||||
return nil
|
||||
}
|
||||
if c.ha == nil || c.standby {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate the token is a root token
|
||||
req := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "sys/step-down",
|
||||
ClientToken: token,
|
||||
}
|
||||
|
||||
acl, te, err := c.fetchACLandTokenEntry(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Attempt to use the token (decrement num_uses)
|
||||
if te != nil {
|
||||
if err := c.tokenStore.UseToken(te); err != nil {
|
||||
c.logger.Printf("[ERR] core: failed to use token: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that this operation is allowed
|
||||
allowed, rootPrivs := acl.AllowOperation(req.Operation, req.Path)
|
||||
if !allowed {
|
||||
return logical.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// We always require root privileges for this operation
|
||||
if !rootPrivs {
|
||||
return logical.ErrPermissionDenied
|
||||
}
|
||||
|
||||
select {
|
||||
case c.manualStepDownCh <- struct{}{}:
|
||||
default:
|
||||
c.logger.Printf("[WARN] core: manual step-down operation already queued")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sealInternal is an internal method used to seal the vault. It does not do
|
||||
// any authorization checking. The stateLock must be held prior to calling.
|
||||
func (c *Core) sealInternal() error {
|
||||
// Enable that we are sealed to prevent furthur transactions
|
||||
c.sealed = true
|
||||
@@ -1227,6 +1287,7 @@ func (c *Core) sealInternal() error {
|
||||
return err
|
||||
}
|
||||
c.logger.Printf("[INFO] core: vault is sealed")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1336,8 +1397,9 @@ func (c *Core) preSeal() error {
|
||||
// runStandby is a long running routine that is used when an HA backend
|
||||
// is enabled. It waits until we are leader and switches this Vault to
|
||||
// active.
|
||||
func (c *Core) runStandby(doneCh, stopCh chan struct{}) {
|
||||
func (c *Core) runStandby(doneCh, stopCh, manualStepDownCh chan struct{}) {
|
||||
defer close(doneCh)
|
||||
defer close(manualStepDownCh)
|
||||
c.logger.Printf("[INFO] core: entering standby mode")
|
||||
|
||||
// Monitor for key rotation
|
||||
@@ -1401,11 +1463,15 @@ func (c *Core) runStandby(doneCh, stopCh chan struct{}) {
|
||||
}
|
||||
|
||||
// Monitor a loss of leadership
|
||||
var manualStepDown bool
|
||||
select {
|
||||
case <-leaderLostCh:
|
||||
c.logger.Printf("[WARN] core: leadership lost, stopping active operation")
|
||||
case <-stopCh:
|
||||
c.logger.Printf("[WARN] core: stopping active operation")
|
||||
case <-manualStepDownCh:
|
||||
c.logger.Printf("[WARN] core: stepping down from active operation to standby")
|
||||
manualStepDown = true
|
||||
}
|
||||
|
||||
// Clear ourself as leader
|
||||
@@ -1426,6 +1492,12 @@ func (c *Core) runStandby(doneCh, stopCh chan struct{}) {
|
||||
if preSealErr != nil {
|
||||
c.logger.Printf("[ERR] core: pre-seal teardown failed: %v", err)
|
||||
}
|
||||
|
||||
// If we've merely stepped down, we could instantly grab the lock
|
||||
// again. Give the other nodes a chance.
|
||||
if manualStepDown {
|
||||
time.Sleep(manualStepDownSleepPeriod)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1106,9 +1106,6 @@ func TestCore_Standby_Seal(t *testing.T) {
|
||||
// Wait for core to become active
|
||||
testWaitActive(t, core)
|
||||
|
||||
// Ensure that the original clean function has stopped running
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Check the leader is local
|
||||
isLeader, advertise, err := core.Leader()
|
||||
if err != nil {
|
||||
@@ -1183,6 +1180,180 @@ func TestCore_Standby_Seal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCore_StepDown(t *testing.T) {
|
||||
// Create the first core and initialize it
|
||||
inm := physical.NewInmem()
|
||||
inmha := physical.NewInmemHA()
|
||||
advertiseOriginal := "http://127.0.0.1:8200"
|
||||
core, err := NewCore(&CoreConfig{
|
||||
Physical: inm,
|
||||
HAPhysical: inmha,
|
||||
AdvertiseAddr: advertiseOriginal,
|
||||
DisableMlock: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
key, root := TestCoreInit(t, core)
|
||||
if _, err := core.Unseal(TestKeyCopy(key)); err != nil {
|
||||
t.Fatalf("unseal err: %s", err)
|
||||
}
|
||||
|
||||
// Verify unsealed
|
||||
sealed, err := core.Sealed()
|
||||
if err != nil {
|
||||
t.Fatalf("err checking seal status: %s", err)
|
||||
}
|
||||
if sealed {
|
||||
t.Fatal("should not be sealed")
|
||||
}
|
||||
|
||||
// Wait for core to become active
|
||||
testWaitActive(t, core)
|
||||
|
||||
// Check the leader is local
|
||||
isLeader, advertise, err := core.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !isLeader {
|
||||
t.Fatalf("should be leader")
|
||||
}
|
||||
if advertise != advertiseOriginal {
|
||||
t.Fatalf("Bad advertise: %v", advertise)
|
||||
}
|
||||
|
||||
// Create the second core and initialize it
|
||||
advertiseOriginal2 := "http://127.0.0.1:8500"
|
||||
core2, err := NewCore(&CoreConfig{
|
||||
Physical: inm,
|
||||
HAPhysical: inmha,
|
||||
AdvertiseAddr: advertiseOriginal2,
|
||||
DisableMlock: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if _, err := core2.Unseal(TestKeyCopy(key)); err != nil {
|
||||
t.Fatalf("unseal err: %s", err)
|
||||
}
|
||||
|
||||
// Verify unsealed
|
||||
sealed, err = core2.Sealed()
|
||||
if err != nil {
|
||||
t.Fatalf("err checking seal status: %s", err)
|
||||
}
|
||||
if sealed {
|
||||
t.Fatal("should not be sealed")
|
||||
}
|
||||
|
||||
// Core2 should be in standby
|
||||
standby, err := core2.Standby()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !standby {
|
||||
t.Fatalf("should be standby")
|
||||
}
|
||||
|
||||
// Check the leader is not local
|
||||
isLeader, advertise, err = core2.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if isLeader {
|
||||
t.Fatalf("should not be leader")
|
||||
}
|
||||
if advertise != advertiseOriginal {
|
||||
t.Fatalf("Bad advertise: %v", advertise)
|
||||
}
|
||||
|
||||
// Step down core
|
||||
err = core.StepDown(root)
|
||||
if err != nil {
|
||||
t.Fatal("error stepping down core 1")
|
||||
}
|
||||
|
||||
// Give time to switch leaders
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Core1 should be in standby
|
||||
standby, err = core.Standby()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !standby {
|
||||
t.Fatalf("should be standby")
|
||||
}
|
||||
|
||||
// Check the leader is core2
|
||||
isLeader, advertise, err = core2.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !isLeader {
|
||||
t.Fatalf("should be leader")
|
||||
}
|
||||
if advertise != advertiseOriginal2 {
|
||||
t.Fatalf("Bad advertise: %v", advertise)
|
||||
}
|
||||
|
||||
// Check the leader is not local
|
||||
isLeader, advertise, err = core.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if isLeader {
|
||||
t.Fatalf("should not be leader")
|
||||
}
|
||||
if advertise != advertiseOriginal2 {
|
||||
t.Fatalf("Bad advertise: %v", advertise)
|
||||
}
|
||||
|
||||
// Step down core2
|
||||
err = core2.StepDown(root)
|
||||
if err != nil {
|
||||
t.Fatal("error stepping down core 1")
|
||||
}
|
||||
|
||||
// Give time to switch leaders -- core 1 will still be waiting on its
|
||||
// cooling off period so give it a full 10 seconds to recover
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Core2 should be in standby
|
||||
standby, err = core2.Standby()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !standby {
|
||||
t.Fatalf("should be standby")
|
||||
}
|
||||
|
||||
// Check the leader is core1
|
||||
isLeader, advertise, err = core.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !isLeader {
|
||||
t.Fatalf("should be leader")
|
||||
}
|
||||
if advertise != advertiseOriginal {
|
||||
t.Fatalf("Bad advertise: %v", advertise)
|
||||
}
|
||||
|
||||
// Check the leader is not local
|
||||
isLeader, advertise, err = core2.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if isLeader {
|
||||
t.Fatalf("should not be leader")
|
||||
}
|
||||
if advertise != advertiseOriginal {
|
||||
t.Fatalf("Bad advertise: %v", advertise)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCore_CleanLeaderPrefix(t *testing.T) {
|
||||
// Create the first core and initialize it
|
||||
inm := physical.NewInmem()
|
||||
|
||||
@@ -310,7 +310,7 @@ func (m *ExpirationManager) Renew(leaseID string, increment time.Duration) (*log
|
||||
// RenewToken is used to renew a token which does not need to
|
||||
// invoke a logical backend.
|
||||
func (m *ExpirationManager) RenewToken(req *logical.Request, source string, token string,
|
||||
increment time.Duration) (*logical.Auth, error) {
|
||||
increment time.Duration) (*logical.Response, error) {
|
||||
defer metrics.MeasureSince([]string{"expire", "renew-token"}, time.Now())
|
||||
// Compute the Lease ID
|
||||
leaseID := path.Join(source, m.tokenStore.SaltID(token))
|
||||
@@ -333,12 +333,20 @@ func (m *ExpirationManager) RenewToken(req *logical.Request, source string, toke
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fast-path if there is no renewal
|
||||
if resp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return &logical.Response{
|
||||
Data: resp.Data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if resp.Auth == nil || !resp.Auth.LeaseEnabled() {
|
||||
return resp.Auth, nil
|
||||
return &logical.Response{
|
||||
Auth: resp.Auth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Attach the ClientToken
|
||||
@@ -355,7 +363,9 @@ func (m *ExpirationManager) RenewToken(req *logical.Request, source string, toke
|
||||
|
||||
// Update the expiration time
|
||||
m.updatePending(le, resp.Auth.LeaseTotal())
|
||||
return resp.Auth, nil
|
||||
return &logical.Response{
|
||||
Auth: resp.Auth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Register is used to take a request and response with an associated
|
||||
|
||||
@@ -424,7 +424,7 @@ func TestExpiration_RenewToken(t *testing.T) {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if auth.ClientToken != out.ClientToken {
|
||||
if auth.ClientToken != out.Auth.ClientToken {
|
||||
t.Fatalf("Bad: %#v", out)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +246,7 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) logical.Backend
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.handlePolicyList,
|
||||
logical.ListOperation: b.handlePolicyList,
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["policy-list"][0]),
|
||||
@@ -465,6 +466,8 @@ func (b *SystemBackend) handleMount(
|
||||
logicalType := data.Get("type").(string)
|
||||
description := data.Get("description").(string)
|
||||
|
||||
path = sanitizeMountPath(path)
|
||||
|
||||
var config MountConfig
|
||||
|
||||
var apiConfig struct {
|
||||
@@ -561,6 +564,8 @@ func (b *SystemBackend) handleUnmount(
|
||||
return logical.ErrorResponse("path cannot be blank"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
suffix = sanitizeMountPath(suffix)
|
||||
|
||||
// Attempt unmount
|
||||
if err := b.Core.unmount(suffix); err != nil {
|
||||
b.Backend.Logger().Printf("[ERR] sys: unmount '%s' failed: %v", suffix, err)
|
||||
@@ -582,6 +587,9 @@ func (b *SystemBackend) handleRemount(
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
fromPath = sanitizeMountPath(fromPath)
|
||||
toPath = sanitizeMountPath(toPath)
|
||||
|
||||
// Attempt remount
|
||||
if err := b.Core.remount(fromPath, toPath); err != nil {
|
||||
b.Backend.Logger().Printf("[ERR] sys: remount '%s' to '%s' failed: %v", fromPath, toPath, err)
|
||||
@@ -601,9 +609,7 @@ func (b *SystemBackend) handleMountTuneRead(
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
path = sanitizeMountPath(path)
|
||||
|
||||
sysView := b.Core.router.MatchingSystemView(path)
|
||||
if sysView == nil {
|
||||
@@ -632,9 +638,7 @@ func (b *SystemBackend) handleMountTuneWrite(
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
path = sanitizeMountPath(path)
|
||||
|
||||
// Prevent protected paths from being changed
|
||||
for _, p := range untunableMounts {
|
||||
@@ -776,6 +780,8 @@ func (b *SystemBackend) handleEnableAuth(
|
||||
logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
path = sanitizeMountPath(path)
|
||||
|
||||
// Create the mount entry
|
||||
me := &MountEntry{
|
||||
Path: path,
|
||||
@@ -799,6 +805,8 @@ func (b *SystemBackend) handleDisableAuth(
|
||||
return logical.ErrorResponse("path cannot be blank"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
suffix = sanitizeMountPath(suffix)
|
||||
|
||||
// Attempt disable
|
||||
if err := b.Core.disableCredential(suffix); err != nil {
|
||||
b.Backend.Logger().Printf("[ERR] sys: disable auth '%s' failed: %v", suffix, err)
|
||||
@@ -815,7 +823,12 @@ func (b *SystemBackend) handlePolicyList(
|
||||
|
||||
// Add the special "root" policy
|
||||
policies = append(policies, "root")
|
||||
return logical.ListResponse(policies), err
|
||||
resp := logical.ListResponse(policies)
|
||||
|
||||
// Backwords compatibility
|
||||
resp.Data["policies"] = resp.Data["keys"]
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// handlePolicyRead handles the "policy/<name>" endpoint to read a policy
|
||||
@@ -902,9 +915,7 @@ func (b *SystemBackend) handleAuditHash(
|
||||
return logical.ErrorResponse("the \"input\" parameter is empty"), nil
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
path = sanitizeMountPath(path)
|
||||
|
||||
hash, err := b.Core.auditBroker.GetHash(path, input)
|
||||
if err != nil {
|
||||
@@ -1083,6 +1094,18 @@ func (b *SystemBackend) handleRotate(
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func sanitizeMountPath(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(path, "/") {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
const sysHelpRoot = `
|
||||
The system backend is built-in to Vault and cannot be remounted or
|
||||
unmounted. It contains the paths that are used to configure Vault itself
|
||||
|
||||
@@ -431,7 +431,8 @@ func TestSystemBackend_policyList(t *testing.T) {
|
||||
}
|
||||
|
||||
exp := map[string]interface{}{
|
||||
"keys": []string{"default", "root"},
|
||||
"keys": []string{"default", "root"},
|
||||
"policies": []string{"default", "root"},
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data, exp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
|
||||
@@ -483,7 +484,8 @@ func TestSystemBackend_policyCRUD(t *testing.T) {
|
||||
}
|
||||
|
||||
exp = map[string]interface{}{
|
||||
"keys": []string{"default", "foo", "root"},
|
||||
"keys": []string{"default", "foo", "root"},
|
||||
"policies": []string{"default", "foo", "root"},
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data, exp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
|
||||
@@ -517,7 +519,8 @@ func TestSystemBackend_policyCRUD(t *testing.T) {
|
||||
}
|
||||
|
||||
exp = map[string]interface{}{
|
||||
"keys": []string{"default", "root"},
|
||||
"keys": []string{"default", "root"},
|
||||
"policies": []string{"default", "root"},
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data, exp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
|
||||
|
||||
@@ -74,6 +74,12 @@ func Parse(rules string) (*Policy, error) {
|
||||
|
||||
// Validate the path policy
|
||||
for _, pc := range p.Paths {
|
||||
// Strip a leading '/' as paths in Vault start after the / in the API
|
||||
// path
|
||||
if len(pc.Prefix) > 0 && pc.Prefix[0] == '/' {
|
||||
pc.Prefix = pc.Prefix[1:]
|
||||
}
|
||||
|
||||
// Strip the glob character if found
|
||||
if strings.HasSuffix(pc.Prefix, "*") {
|
||||
pc.Prefix = strings.TrimSuffix(pc.Prefix, "*")
|
||||
|
||||
@@ -80,7 +80,8 @@ path "prod/version" {
|
||||
}
|
||||
|
||||
# Read access to foobar
|
||||
path "foo/bar" {
|
||||
# Also tests stripping of leading slash
|
||||
path "/foo/bar" {
|
||||
policy = "read"
|
||||
}
|
||||
|
||||
|
||||
@@ -1047,16 +1047,7 @@ func (ts *TokenStore) handleRenew(
|
||||
}
|
||||
|
||||
// Renew the token and its children
|
||||
auth, err := ts.expiration.RenewToken(req, te.Path, te.ID, increment)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &logical.Response{
|
||||
Auth: auth,
|
||||
}
|
||||
return resp, nil
|
||||
return ts.expiration.RenewToken(req, te.Path, te.ID, increment)
|
||||
}
|
||||
|
||||
func (ts *TokenStore) destroyCubbyhole(saltedID string) error {
|
||||
|
||||
@@ -11,7 +11,9 @@ description: |-
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Seals the Vault. In HA mode, only an active node can be sealed. Standby nodes should be restarted to get the same effect.
|
||||
Seals the Vault. In HA mode, only an active node can be sealed. Standby
|
||||
nodes should be restarted to get the same effect. Requires a token with
|
||||
`root` policy or `sudo` capability on the path.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
|
||||
33
website/source/docs/http/sys-step-down.html.md
Normal file
33
website/source/docs/http/sys-step-down.html.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
layout: "http"
|
||||
page_title: "HTTP API: /sys/step-down"
|
||||
sidebar_current: "docs-http-ha-step-down"
|
||||
description: |-
|
||||
The '/sys/step-down' endpoint causes the node to give up active status.
|
||||
---
|
||||
|
||||
# /sys/seal
|
||||
|
||||
<dl>
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Forces the node to give up active status. If the node does not have active
|
||||
status, this endpoint does nothing. Note that the node will sleep for ten
|
||||
seconds before attempting to grab the active lock again, but if no standby
|
||||
nodes grab the active lock in the interim, the same node may become the
|
||||
active node again. Requires a token with `root` policy or `sudo` capability
|
||||
on the path.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>PUT</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
None
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>A `204` response code.
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -15,13 +15,14 @@ the configured physical storage for Vault. It is mounted at the `cubbyhole/`
|
||||
prefix by default and cannot be mounted elsewhere or removed.
|
||||
|
||||
This backend differs from the `generic` backend in that the `generic` backend's
|
||||
values are accessible to any token with read privileges on that path. In this
|
||||
backend, paths are scoped per token; no token can read secrets placed in
|
||||
another token's cubbyhole. When the token expires, its cubbyhole is destroyed.
|
||||
values are accessible to any token with read privileges on that path. In
|
||||
`cubbyhole`, paths are scoped per token; no token can access another token's
|
||||
cubbyhole, whether to read, write, list, or for any other operation. When the
|
||||
token expires, its cubbyhole is destroyed.
|
||||
|
||||
Also unlike the `generic` backend, because the cubbyhole's lifetime is linked
|
||||
to an authentication token, there is no concept of a lease or lease TTL for
|
||||
values contained in the token's cubbyhole.
|
||||
to that of an authentication token, there is no concept of a TTL for values
|
||||
contained in the token's cubbyhole.
|
||||
|
||||
Writing to a key in the `cubbyhole` backend will replace the old value;
|
||||
the sub-fields are not merged together.
|
||||
@@ -96,9 +97,7 @@ As expected, the value previously set is returned to us.
|
||||
<dd>
|
||||
Returns a list of secret entries at the specified location. Folders are
|
||||
suffixed with `/`. The input must be a folder; list on a file will not
|
||||
return a value. Note that no policy-based filtering is performed on
|
||||
returned keys; it is not recommended to put sensitive or secret values as
|
||||
key names. The values themselves are not accessible via this command.
|
||||
return a value. The values themselves are not accessible via this command.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
|
||||
@@ -206,8 +206,12 @@ $ vault write ssh/roles/dynamic_key_role \
|
||||
Success! Data written to: ssh/roles/dynamic_key_role
|
||||
```
|
||||
|
||||
`cidr_list` is optional and defaults to the zero address (0.0.0.0/0), e.g. all
|
||||
hosts.
|
||||
`cidr_list` is a comma separated list of CIDR blocks for which a role can generate
|
||||
credentials. If this is empty, the role can only generate credentials if it belongs
|
||||
to the set of zero-address roles.
|
||||
|
||||
Zero-address roles, configured via `/ssh/config/zeroaddress` endpoint, takes comma separated list
|
||||
of role names that can generate credentials for any IP address.
|
||||
|
||||
Use the `install_script` option to provide an install script if the remote
|
||||
hosts do not resemble a typical Linux machine. The default script is compiled
|
||||
@@ -388,7 +392,6 @@ username@ip:~$
|
||||
(String)
|
||||
Comma separated list of CIDR blocks for which the role is
|
||||
applicable for. CIDR blocks can belong to more than one role.
|
||||
Defaults to the zero address (0.0.0.0/0).
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">exclude_cidr_list</span>
|
||||
@@ -559,6 +562,100 @@ username@ip:~$
|
||||
<dd>
|
||||
A `204` response code.
|
||||
</dd>
|
||||
|
||||
### /ssh/config/zeroaddress
|
||||
|
||||
#### GET
|
||||
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Returns the list of configured zero-address roles.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>GET</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/ssh/config/zeroaddress`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>None</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
|
||||
```json
|
||||
{
|
||||
"lease_id":"",
|
||||
"renewable":false,
|
||||
"lease_duration":0,
|
||||
"data":{
|
||||
"roles":[
|
||||
"otp_key_role"
|
||||
]
|
||||
},
|
||||
"warnings":null,
|
||||
"auth":null
|
||||
}
|
||||
```
|
||||
|
||||
</dd>
|
||||
#### POST
|
||||
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Configures zero-address roles.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>POST</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/ssh/config/zeroaddress`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">roles</span>
|
||||
<span class="param-flags">required</span>
|
||||
A string containing comma separated list of role names which allows credentials to be requested
|
||||
for any IP address. CIDR blocks previously registered under these roles will be ignored.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
A `204` response code.
|
||||
</dd>
|
||||
|
||||
#### DELETE
|
||||
|
||||
<dl class="api">
|
||||
<dt>Description</dt>
|
||||
<dd>
|
||||
Deletes the zero-address roles configuration.
|
||||
</dd>
|
||||
|
||||
<dt>Method</dt>
|
||||
<dd>DELETE</dd>
|
||||
|
||||
<dt>URL</dt>
|
||||
<dd>`/ssh/config/zeroaddress`</dd>
|
||||
|
||||
<dt>Parameters</dt>
|
||||
<dd>None</dd>
|
||||
|
||||
<dt>Returns</dt>
|
||||
<dd>
|
||||
A `204` response code.
|
||||
</dd>
|
||||
|
||||
|
||||
|
||||
### /ssh/creds/
|
||||
#### POST
|
||||
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
<li<%= sidebar_current("docs-http-ha-leader") %>>
|
||||
<a href="/docs/http/sys-leader.html">/sys/leader</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-http-ha-step-down") %>>
|
||||
<a href="/docs/http/sys-step-down.html">/sys/step-down</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user