Merge pull request #2226 from hashicorp/b-vault
Improve Vault integration and validation
This commit is contained in:
commit
606bb30863
|
@ -121,6 +121,7 @@ vault {
|
|||
key_file = "/path/to/key/file"
|
||||
tls_server_name = "foobar"
|
||||
tls_skip_verify = true
|
||||
create_from_role = "test_role"
|
||||
}
|
||||
tls {
|
||||
http = true
|
||||
|
|
|
@ -710,6 +710,7 @@ func parseVaultConfig(result **config.VaultConfig, list *ast.ObjectList) error {
|
|||
"ca_file",
|
||||
"ca_path",
|
||||
"cert_file",
|
||||
"create_from_role",
|
||||
"key_file",
|
||||
"tls_server_name",
|
||||
"tls_skip_verify",
|
||||
|
|
|
@ -129,6 +129,7 @@ func TestConfig_Parse(t *testing.T) {
|
|||
Addr: "127.0.0.1:9500",
|
||||
AllowUnauthenticated: &trueValue,
|
||||
Enabled: &falseValue,
|
||||
Role: "test_role",
|
||||
TLSCaFile: "/path/to/ca/file",
|
||||
TLSCaPath: "/path/to/ca",
|
||||
TLSCertFile: "/path/to/cert/file",
|
||||
|
|
|
@ -30,6 +30,13 @@ type VaultConfig struct {
|
|||
// lifetime.
|
||||
Token string `mapstructure:"token"`
|
||||
|
||||
// Role sets the role in which to create tokens from. The Token given to
|
||||
// Nomad does not have to be created from this role but must have "update"
|
||||
// capability on "auth/token/create/<create_from_role>". If this value is
|
||||
// unset and the token is created from a role, the value is defaulted to the
|
||||
// role the token is from.
|
||||
Role string `mapstructure:"create_from_role"`
|
||||
|
||||
// AllowUnauthenticated allows users to submit jobs requiring Vault tokens
|
||||
// without providing a Vault token proving they have access to these
|
||||
// policies.
|
||||
|
@ -99,6 +106,9 @@ func (a *VaultConfig) Merge(b *VaultConfig) *VaultConfig {
|
|||
if b.Token != "" {
|
||||
result.Token = b.Token
|
||||
}
|
||||
if b.Role != "" {
|
||||
result.Role = b.Role
|
||||
}
|
||||
if b.TaskTokenTTL != "" {
|
||||
result.TaskTokenTTL = b.TaskTokenTTL
|
||||
}
|
||||
|
|
200
nomad/vault.go
200
nomad/vault.go
|
@ -47,11 +47,68 @@ const (
|
|||
// vaultRevocationIntv is the interval at which Vault tokens that failed
|
||||
// initial revocation are retried
|
||||
vaultRevocationIntv = 5 * time.Minute
|
||||
|
||||
// vaultCapabilitiesLookupPath is the path to lookup the capabilities of
|
||||
// ones token.
|
||||
vaultCapabilitiesLookupPath = "/sys/capabilities-self"
|
||||
|
||||
// vaultTokenRenewPath is the path used to renew our token
|
||||
vaultTokenRenewPath = "auth/token/renew-self"
|
||||
|
||||
// vaultTokenLookupPath is the path used to lookup a token
|
||||
vaultTokenLookupPath = "auth/token/lookup"
|
||||
|
||||
// vaultTokenLookupSelfPath is the path used to lookup self token
|
||||
vaultTokenLookupSelfPath = "auth/token/lookup-self"
|
||||
|
||||
// vaultTokenRevokePath is the path used to revoke a token
|
||||
vaultTokenRevokePath = "auth/token/revoke-accessor"
|
||||
|
||||
// vaultRoleLookupPath is the path to lookup a role
|
||||
vaultRoleLookupPath = "auth/token/roles/%s"
|
||||
|
||||
// vaultRoleCreatePath is the path to create a token from a role
|
||||
vaultTokenRoleCreatePath = "auth/token/create/%s"
|
||||
)
|
||||
|
||||
var (
|
||||
// vaultUnrecoverableError matches unrecoverable errors
|
||||
vaultUnrecoverableError = regexp.MustCompile(`Code:\s+40(0|3|4)`)
|
||||
|
||||
// vaultCapabilitiesCapability is the expected capability of Nomad's Vault
|
||||
// token on the the path. The token must have at least one of the
|
||||
// capabilities.
|
||||
vaultCapabilitiesCapability = []string{"update", "root"}
|
||||
|
||||
// vaultTokenRenewCapability is the expected capability Nomad's
|
||||
// Vault token should have on the path. The token must have at least one of
|
||||
// the capabilities.
|
||||
vaultTokenRenewCapability = []string{"update", "root"}
|
||||
|
||||
// vaultTokenLookupCapability is the expected capability Nomad's
|
||||
// Vault token should have on the path. The token must have at least one of
|
||||
// the capabilities.
|
||||
vaultTokenLookupCapability = []string{"update", "root"}
|
||||
|
||||
// vaultTokenLookupSelfCapability is the expected capability Nomad's
|
||||
// Vault token should have on the path. The token must have at least one of
|
||||
// the capabilities.
|
||||
vaultTokenLookupSelfCapability = []string{"update", "root"}
|
||||
|
||||
// vaultTokenRevokeCapability is the expected capability Nomad's
|
||||
// Vault token should have on the path. The token must have at least one of
|
||||
// the capabilities.
|
||||
vaultTokenRevokeCapability = []string{"update", "root"}
|
||||
|
||||
// vaultRoleLookupCapability is the the expected capability Nomad's Vault
|
||||
// token should have on the path. The token must have at least one of the
|
||||
// capabilities.
|
||||
vaultRoleLookupCapability = []string{"read", "root"}
|
||||
|
||||
// vaultTokenRoleCreateCapability is the the expected capability Nomad's Vault
|
||||
// token should have on the path. The token must have at least one of the
|
||||
// capabilities.
|
||||
vaultTokenRoleCreateCapability = []string{"update", "root"}
|
||||
)
|
||||
|
||||
// VaultClient is the Servers interface for interfacing with Vault
|
||||
|
@ -417,7 +474,7 @@ func (v *vaultClient) renewalLoop() {
|
|||
|
||||
// Set base values and add some backoff
|
||||
|
||||
v.logger.Printf("[DEBUG] vault: got error or bad auth, so backing off: %v", err)
|
||||
v.logger.Printf("[WARN] vault: got error or bad auth, so backing off: %v", err)
|
||||
switch {
|
||||
case backoff < 5:
|
||||
backoff = 5
|
||||
|
@ -477,8 +534,9 @@ func (v *vaultClient) renew() error {
|
|||
// getWrappingFn returns an appropriate wrapping function for Nomad Servers
|
||||
func (v *vaultClient) getWrappingFn() func(operation, path string) string {
|
||||
createPath := "auth/token/create"
|
||||
if !v.tokenData.Root {
|
||||
createPath = fmt.Sprintf("auth/token/create/%s", v.tokenData.Role)
|
||||
role := v.getRole()
|
||||
if role != "" {
|
||||
createPath = fmt.Sprintf("auth/token/create/%s", role)
|
||||
}
|
||||
|
||||
return func(operation, path string) string {
|
||||
|
@ -497,10 +555,18 @@ func (v *vaultClient) getWrappingFn() func(operation, path string) string {
|
|||
func (v *vaultClient) parseSelfToken() error {
|
||||
// Get the initial lease duration
|
||||
auth := v.client.Auth().Token()
|
||||
self, err := auth.LookupSelf()
|
||||
var self *vapi.Secret
|
||||
|
||||
// Try looking up the token using the self endpoint
|
||||
secret, err := auth.LookupSelf()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup Vault periodic token: %v", err)
|
||||
// Try looking up our token directly
|
||||
self, err = auth.Lookup(v.client.Token())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup Vault periodic token: %v", err)
|
||||
}
|
||||
}
|
||||
self = secret
|
||||
|
||||
// Read and parse the fields
|
||||
var data tokenData
|
||||
|
@ -516,7 +582,28 @@ func (v *vaultClient) parseSelfToken() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Store the token data
|
||||
data.Root = root
|
||||
v.tokenData = &data
|
||||
|
||||
// The criteria that must be met for the token to be valid are as follows:
|
||||
// 1) If token is non-root or is but has a creation ttl
|
||||
// a) The token must be renewable
|
||||
// b) Token must have a non-zero TTL
|
||||
// 2) Must have update capability for "auth/token/lookup/" (used to verify incoming tokens)
|
||||
// 3) Must have update capability for "/auth/token/revoke-accessor/" (used to revoke unneeded tokens)
|
||||
// 4) If configured to create tokens against a role:
|
||||
// a) Must have read capability for "auth/token/roles/<role_name" (Can just attemp a read)
|
||||
// b) Must have update capability for path "auth/token/create/<role_name>"
|
||||
// c) Role must:
|
||||
// 1) Not allow orphans
|
||||
// 2) Must allow tokens to be renewed
|
||||
// 3) Must not have an explicit max TTL
|
||||
// 4) Must have non-zero period
|
||||
// 5) If not configured against a role, the token must be root
|
||||
|
||||
var mErr multierror.Error
|
||||
role := v.getRole()
|
||||
if !root {
|
||||
// All non-root tokens must be renewable
|
||||
if !data.Renewable {
|
||||
|
@ -534,7 +621,7 @@ func (v *vaultClient) parseSelfToken() error {
|
|||
}
|
||||
|
||||
// There must be a valid role since we aren't root
|
||||
if data.Role == "" {
|
||||
if role == "" {
|
||||
multierror.Append(&mErr, fmt.Errorf("token role name must be set when not using a root token"))
|
||||
}
|
||||
|
||||
|
@ -548,18 +635,106 @@ func (v *vaultClient) parseSelfToken() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Check we have the correct capabilities
|
||||
if err := v.validateCapabilities(role, root); err != nil {
|
||||
multierror.Append(&mErr, err)
|
||||
}
|
||||
|
||||
// If given a role validate it
|
||||
if data.Role != "" {
|
||||
if err := v.validateRole(data.Role); err != nil {
|
||||
if role != "" {
|
||||
if err := v.validateRole(role); err != nil {
|
||||
multierror.Append(&mErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
data.Root = root
|
||||
v.tokenData = &data
|
||||
return mErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// getRole returns the role name to be used when creating tokens
|
||||
func (v *vaultClient) getRole() string {
|
||||
if v.config.Role != "" {
|
||||
return v.config.Role
|
||||
}
|
||||
|
||||
return v.tokenData.Role
|
||||
}
|
||||
|
||||
// validateCapabilities checks that Nomad's Vault token has the correct
|
||||
// capabilities.
|
||||
func (v *vaultClient) validateCapabilities(role string, root bool) error {
|
||||
// Check if the token can lookup capabilities.
|
||||
var mErr multierror.Error
|
||||
_, _, err := v.hasCapability(vaultCapabilitiesLookupPath, vaultCapabilitiesCapability)
|
||||
if err != nil {
|
||||
// Check if there is a permission denied
|
||||
if vaultUnrecoverableError.MatchString(err.Error()) {
|
||||
// Since we can't read permissions, we just log a warning that we
|
||||
// can't tell if the Vault token will work
|
||||
msg := fmt.Sprintf("Can not lookup token capabilities. "+
|
||||
"As such certain operations may fail in the future. "+
|
||||
"Please give Nomad a Vault token with one of the following "+
|
||||
"capabilities %q on %q so that the required capabilities can be verified",
|
||||
vaultCapabilitiesCapability, vaultCapabilitiesLookupPath)
|
||||
v.logger.Printf("[WARN] vault: %s", msg)
|
||||
return nil
|
||||
} else {
|
||||
multierror.Append(&mErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// verify is a helper function that verifies the token has one of the
|
||||
// capabilities on the given path and adds an issue to the error
|
||||
verify := func(path string, requiredCaps []string) {
|
||||
ok, caps, err := v.hasCapability(path, requiredCaps)
|
||||
if err != nil {
|
||||
multierror.Append(&mErr, err)
|
||||
} else if !ok {
|
||||
multierror.Append(&mErr,
|
||||
fmt.Errorf("token must have one of the following capabilities %q on %q; has %v", requiredCaps, path, caps))
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we are verifying incoming tokens
|
||||
if !v.config.AllowsUnauthenticated() {
|
||||
verify(vaultTokenLookupPath, vaultTokenLookupCapability)
|
||||
}
|
||||
|
||||
// Verify we can renew our selves tokens
|
||||
verify(vaultTokenRenewPath, vaultTokenRenewCapability)
|
||||
|
||||
// Verify we can revoke tokens
|
||||
verify(vaultTokenRevokePath, vaultTokenRevokeCapability)
|
||||
|
||||
// If we are using a role verify the capability
|
||||
if role != "" {
|
||||
// Verify we can read the role
|
||||
verify(fmt.Sprintf(vaultRoleLookupPath, role), vaultRoleLookupCapability)
|
||||
|
||||
// Verify we can create from the role
|
||||
verify(fmt.Sprintf(vaultTokenRoleCreatePath, role), vaultTokenRoleCreateCapability)
|
||||
}
|
||||
|
||||
return mErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// hasCapability takes a path and returns whether the token has at least one of
|
||||
// the required capabilities on the given path. It also returns the set of
|
||||
// capabilities the token does have as well as any error that occured.
|
||||
func (v *vaultClient) hasCapability(path string, required []string) (bool, []string, error) {
|
||||
caps, err := v.client.Sys().CapabilitiesSelf(path)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
for _, c := range caps {
|
||||
for _, r := range required {
|
||||
if c == r {
|
||||
return true, caps, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, caps, nil
|
||||
}
|
||||
|
||||
// validateRole contacts Vault and checks that the given Vault role is valid for
|
||||
// the purposes of being used by Nomad
|
||||
func (v *vaultClient) validateRole(role string) error {
|
||||
|
@ -679,12 +854,13 @@ func (v *vaultClient) CreateToken(ctx context.Context, a *structs.Allocation, ta
|
|||
// token or a role based token
|
||||
var secret *vapi.Secret
|
||||
var err error
|
||||
if v.tokenData.Root {
|
||||
role := v.getRole()
|
||||
if v.tokenData.Root && role == "" {
|
||||
req.Period = v.childTTL
|
||||
secret, err = v.auth.Create(req)
|
||||
} else {
|
||||
// Make the token using the role
|
||||
secret, err = v.auth.CreateWithRole(req, v.tokenData.Role)
|
||||
secret, err = v.auth.CreateWithRole(req, v.getRole())
|
||||
}
|
||||
|
||||
// Determine whether it is unrecoverable
|
||||
|
|
|
@ -21,25 +21,129 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// authPolicy is a policy that allows token creation operations
|
||||
authPolicy = `path "auth/token/create/test" {
|
||||
capabilities = ["create", "update"]
|
||||
// nomadRoleManagementPolicy is a policy that allows nomad to manage tokens
|
||||
nomadRoleManagementPolicy = `
|
||||
path "auth/token/renew-self" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
path "auth/token/lookup/*" {
|
||||
capabilities = ["read"]
|
||||
path "auth/token/lookup" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
path "auth/token/roles/test" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "/auth/token/revoke-accessor/*" {
|
||||
path "auth/token/revoke-accessor" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
`
|
||||
|
||||
// tokenLookupPolicy allows a token to be looked up
|
||||
tokenLookupPolicy = `
|
||||
path "auth/token/lookup" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
`
|
||||
|
||||
// nomadRoleCreatePolicy gives the ability to create the role and derive tokens
|
||||
// from the test role
|
||||
nomadRoleCreatePolicy = `
|
||||
path "auth/token/create/test" {
|
||||
capabilities = ["create", "update"]
|
||||
}
|
||||
`
|
||||
|
||||
// secretPolicy gives access to the secret mount
|
||||
secretPolicy = `
|
||||
path "secret/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// defaultTestVaultWhitelistRoleAndToken creates a test Vault role and returns a token
|
||||
// created in that role
|
||||
func defaultTestVaultWhitelistRoleAndToken(v *testutil.TestVault, t *testing.T, rolePeriod int) string {
|
||||
vaultPolicies := map[string]string{
|
||||
"nomad-role-create": nomadRoleCreatePolicy,
|
||||
"nomad-role-management": nomadRoleManagementPolicy,
|
||||
}
|
||||
d := make(map[string]interface{}, 2)
|
||||
d["allowed_policies"] = "nomad-role-create,nomad-role-management"
|
||||
d["period"] = rolePeriod
|
||||
return testVaultRoleAndToken(v, t, vaultPolicies, d,
|
||||
[]string{"nomad-role-create", "nomad-role-management"})
|
||||
}
|
||||
|
||||
// defaultTestVaultBlacklistRoleAndToken creates a test Vault role using
|
||||
// disallowed_policies and returns a token created in that role
|
||||
func defaultTestVaultBlacklistRoleAndToken(v *testutil.TestVault, t *testing.T, rolePeriod int) string {
|
||||
vaultPolicies := map[string]string{
|
||||
"nomad-role-create": nomadRoleCreatePolicy,
|
||||
"nomad-role-management": nomadRoleManagementPolicy,
|
||||
"secrets": secretPolicy,
|
||||
}
|
||||
|
||||
// Create the role
|
||||
d := make(map[string]interface{}, 2)
|
||||
d["disallowed_policies"] = "nomad-role-create"
|
||||
d["period"] = rolePeriod
|
||||
testVaultRoleAndToken(v, t, vaultPolicies, d, []string{"default"})
|
||||
|
||||
// Create a token that can use the role
|
||||
a := v.Client.Auth().Token()
|
||||
req := &vapi.TokenCreateRequest{
|
||||
Policies: []string{"nomad-role-create", "nomad-role-management"},
|
||||
}
|
||||
s, err := a.Create(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create child token: %v", err)
|
||||
}
|
||||
|
||||
if s == nil || s.Auth == nil {
|
||||
t.Fatalf("bad secret response: %+v", s)
|
||||
}
|
||||
|
||||
return s.Auth.ClientToken
|
||||
}
|
||||
|
||||
// testVaultRoleAndToken writes the vaultPolicies to vault and then creates a
|
||||
// test role with the passed data. After that it derives a token from the role
|
||||
// with the tokenPolicies
|
||||
func testVaultRoleAndToken(v *testutil.TestVault, t *testing.T, vaultPolicies map[string]string,
|
||||
data map[string]interface{}, tokenPolicies []string) string {
|
||||
// Write the policies
|
||||
sys := v.Client.Sys()
|
||||
for p, data := range vaultPolicies {
|
||||
if err := sys.PutPolicy(p, data); err != nil {
|
||||
t.Fatalf("failed to create %q policy: %v", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Build a role
|
||||
l := v.Client.Logical()
|
||||
l.Write("auth/token/roles/test", data)
|
||||
|
||||
// Create a new token with the role
|
||||
a := v.Client.Auth().Token()
|
||||
req := vapi.TokenCreateRequest{
|
||||
Policies: tokenPolicies,
|
||||
}
|
||||
s, err := a.CreateWithRole(&req, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create child token: %v", err)
|
||||
}
|
||||
|
||||
// Get the client token
|
||||
if s == nil || s.Auth == nil {
|
||||
t.Fatalf("bad secret response: %+v", s)
|
||||
}
|
||||
|
||||
return s.Auth.ClientToken
|
||||
}
|
||||
|
||||
func TestVaultClient_BadConfig(t *testing.T) {
|
||||
conf := &config.VaultConfig{}
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
@ -96,13 +200,17 @@ func TestVaultClient_ValidateRole(t *testing.T) {
|
|||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
vaultPolicies := map[string]string{
|
||||
"nomad-role-create": nomadRoleCreatePolicy,
|
||||
"nomad-role-management": nomadRoleManagementPolicy,
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"allowed_policies": "default,root",
|
||||
"orphan": true,
|
||||
"renewable": true,
|
||||
"explicit_max_ttl": 10,
|
||||
}
|
||||
v.Config.Token = testVaultRoleAndToken(v, t, data)
|
||||
v.Config.Token = testVaultRoleAndToken(v, t, vaultPolicies, data, nil)
|
||||
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
v.Config.ConnectionRetryIntv = 100 * time.Millisecond
|
||||
|
@ -139,6 +247,59 @@ func TestVaultClient_ValidateRole(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestVaultClient_ValidateToken(t *testing.T) {
|
||||
v := testutil.NewTestVault(t).Start()
|
||||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
vaultPolicies := map[string]string{
|
||||
"nomad-role-create": nomadRoleCreatePolicy,
|
||||
"token-lookup": tokenLookupPolicy,
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"allowed_policies": "token-lookup,nomad-role-create",
|
||||
"period": 10,
|
||||
}
|
||||
v.Config.Token = testVaultRoleAndToken(v, t, vaultPolicies, data, []string{"token-lookup", "nomad-role-create"})
|
||||
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
v.Config.ConnectionRetryIntv = 100 * time.Millisecond
|
||||
client, err := NewVaultClient(v.Config, logger, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build vault client: %v", err)
|
||||
}
|
||||
defer client.Stop()
|
||||
|
||||
// Wait for an error
|
||||
var conn bool
|
||||
var connErr error
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
conn, connErr = client.ConnectionEstablished()
|
||||
if conn {
|
||||
return false, fmt.Errorf("Should not connect")
|
||||
}
|
||||
|
||||
if connErr == nil {
|
||||
return false, fmt.Errorf("expect an error")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("bad: %v", err)
|
||||
})
|
||||
|
||||
errStr := connErr.Error()
|
||||
if !strings.Contains(errStr, vaultTokenRevokePath) {
|
||||
t.Fatalf("Expect orphan error")
|
||||
}
|
||||
if !strings.Contains(errStr, fmt.Sprintf(vaultRoleLookupPath, "test")) {
|
||||
t.Fatalf("Expect explicit max ttl error")
|
||||
}
|
||||
if !strings.Contains(errStr, "token must have one of the following") {
|
||||
t.Fatalf("Expect explicit max ttl error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultClient_SetActive(t *testing.T) {
|
||||
v := testutil.NewTestVault(t).Start()
|
||||
defer v.Stop()
|
||||
|
@ -176,7 +337,7 @@ func TestVaultClient_SetConfig(t *testing.T) {
|
|||
defer v2.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
v2.Config.Token = defaultTestVaultRoleAndToken(v2, t, 20)
|
||||
v2.Config.Token = defaultTestVaultWhitelistRoleAndToken(v2, t, 20)
|
||||
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
client, err := NewVaultClient(v.Config, logger, nil)
|
||||
|
@ -198,55 +359,17 @@ func TestVaultClient_SetConfig(t *testing.T) {
|
|||
|
||||
waitForConnection(client, t)
|
||||
|
||||
if client.tokenData == nil || len(client.tokenData.Policies) != 2 {
|
||||
if client.tokenData == nil || len(client.tokenData.Policies) != 3 {
|
||||
t.Fatalf("unexpected token: %v", client.tokenData)
|
||||
}
|
||||
}
|
||||
|
||||
// defaultTestVaultRoleAndToken creates a test Vault role and returns a token
|
||||
// created in that role
|
||||
func defaultTestVaultRoleAndToken(v *testutil.TestVault, t *testing.T, rolePeriod int) string {
|
||||
d := make(map[string]interface{}, 2)
|
||||
d["allowed_policies"] = "auth"
|
||||
d["period"] = rolePeriod
|
||||
return testVaultRoleAndToken(v, t, d)
|
||||
}
|
||||
|
||||
// testVaultRoleAndToken creates a test Vault role with the specified data and
|
||||
// returns a token created in that role
|
||||
func testVaultRoleAndToken(v *testutil.TestVault, t *testing.T, data map[string]interface{}) string {
|
||||
// Build the auth policy
|
||||
sys := v.Client.Sys()
|
||||
if err := sys.PutPolicy("auth", authPolicy); err != nil {
|
||||
t.Fatalf("failed to create auth policy: %v", err)
|
||||
}
|
||||
|
||||
// Build a role
|
||||
l := v.Client.Logical()
|
||||
l.Write("auth/token/roles/test", data)
|
||||
|
||||
// Create a new token with the role
|
||||
a := v.Client.Auth().Token()
|
||||
req := vapi.TokenCreateRequest{}
|
||||
s, err := a.CreateWithRole(&req, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create child token: %v", err)
|
||||
}
|
||||
|
||||
// Get the client token
|
||||
if s == nil || s.Auth == nil {
|
||||
t.Fatalf("bad secret response: %+v", s)
|
||||
}
|
||||
|
||||
return s.Auth.ClientToken
|
||||
}
|
||||
|
||||
func TestVaultClient_RenewalLoop(t *testing.T) {
|
||||
v := testutil.NewTestVault(t).Start()
|
||||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
v.Config.Token = defaultTestVaultRoleAndToken(v, t, 5)
|
||||
v.Config.Token = defaultTestVaultWhitelistRoleAndToken(v, t, 5)
|
||||
|
||||
// Start the client
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
@ -386,7 +509,7 @@ func TestVaultClient_LookupToken_Role(t *testing.T) {
|
|||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
v.Config.Token = defaultTestVaultRoleAndToken(v, t, 5)
|
||||
v.Config.Token = defaultTestVaultWhitelistRoleAndToken(v, t, 5)
|
||||
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
client, err := NewVaultClient(v.Config, logger, nil)
|
||||
|
@ -409,7 +532,7 @@ func TestVaultClient_LookupToken_Role(t *testing.T) {
|
|||
t.Fatalf("failed to parse policies: %v", err)
|
||||
}
|
||||
|
||||
expected := []string{"auth", "default"}
|
||||
expected := []string{"default", "nomad-role-create", "nomad-role-management"}
|
||||
if !reflect.DeepEqual(policies, expected) {
|
||||
t.Fatalf("Unexpected policies; got %v; want %v", policies, expected)
|
||||
}
|
||||
|
@ -543,12 +666,12 @@ func TestVaultClient_CreateToken_Root(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestVaultClient_CreateToken_Role(t *testing.T) {
|
||||
func TestVaultClient_CreateToken_Whitelist_Role(t *testing.T) {
|
||||
v := testutil.NewTestVault(t).Start()
|
||||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
v.Config.Token = defaultTestVaultRoleAndToken(v, t, 5)
|
||||
v.Config.Token = defaultTestVaultWhitelistRoleAndToken(v, t, 5)
|
||||
|
||||
// Start the client
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
@ -590,12 +713,110 @@ func TestVaultClient_CreateToken_Role(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestVaultClient_CreateToken_Root_Target_Role(t *testing.T) {
|
||||
v := testutil.NewTestVault(t).Start()
|
||||
defer v.Stop()
|
||||
|
||||
// Create the test role
|
||||
defaultTestVaultWhitelistRoleAndToken(v, t, 5)
|
||||
|
||||
// Target the test role
|
||||
v.Config.Role = "test"
|
||||
|
||||
// Start the client
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
client, err := NewVaultClient(v.Config, logger, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build vault client: %v", err)
|
||||
}
|
||||
client.SetActive(true)
|
||||
defer client.Stop()
|
||||
|
||||
waitForConnection(client, t)
|
||||
|
||||
// Create an allocation that requires a Vault policy
|
||||
a := mock.Alloc()
|
||||
task := a.Job.TaskGroups[0].Tasks[0]
|
||||
task.Vault = &structs.Vault{Policies: []string{"default"}}
|
||||
|
||||
s, err := client.CreateToken(context.Background(), a, task.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateToken failed: %v", err)
|
||||
}
|
||||
|
||||
// Ensure that created secret is a wrapped token
|
||||
if s == nil || s.WrapInfo == nil {
|
||||
t.Fatalf("Bad secret: %#v", s)
|
||||
}
|
||||
|
||||
d, err := time.ParseDuration(vaultTokenCreateTTL)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %v", err)
|
||||
}
|
||||
|
||||
if s.WrapInfo.WrappedAccessor == "" {
|
||||
t.Fatalf("Bad accessor: %v", s.WrapInfo.WrappedAccessor)
|
||||
} else if s.WrapInfo.Token == "" {
|
||||
t.Fatalf("Bad token: %v", s.WrapInfo.WrappedAccessor)
|
||||
} else if s.WrapInfo.TTL != int(d.Seconds()) {
|
||||
t.Fatalf("Bad ttl: %v", s.WrapInfo.WrappedAccessor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultClient_CreateToken_Blacklist_Role(t *testing.T) {
|
||||
v := testutil.NewTestVault(t).Start()
|
||||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
v.Config.Token = defaultTestVaultBlacklistRoleAndToken(v, t, 5)
|
||||
v.Config.Role = "test"
|
||||
|
||||
// Start the client
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
client, err := NewVaultClient(v.Config, logger, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build vault client: %v", err)
|
||||
}
|
||||
client.SetActive(true)
|
||||
defer client.Stop()
|
||||
|
||||
waitForConnection(client, t)
|
||||
|
||||
// Create an allocation that requires a Vault policy
|
||||
a := mock.Alloc()
|
||||
task := a.Job.TaskGroups[0].Tasks[0]
|
||||
task.Vault = &structs.Vault{Policies: []string{"secrets"}}
|
||||
|
||||
s, err := client.CreateToken(context.Background(), a, task.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateToken failed: %v", err)
|
||||
}
|
||||
|
||||
// Ensure that created secret is a wrapped token
|
||||
if s == nil || s.WrapInfo == nil {
|
||||
t.Fatalf("Bad secret: %#v", s)
|
||||
}
|
||||
|
||||
d, err := time.ParseDuration(vaultTokenCreateTTL)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %v", err)
|
||||
}
|
||||
|
||||
if s.WrapInfo.WrappedAccessor == "" {
|
||||
t.Fatalf("Bad accessor: %v", s.WrapInfo.WrappedAccessor)
|
||||
} else if s.WrapInfo.Token == "" {
|
||||
t.Fatalf("Bad token: %v", s.WrapInfo.WrappedAccessor)
|
||||
} else if s.WrapInfo.TTL != int(d.Seconds()) {
|
||||
t.Fatalf("Bad ttl: %v", s.WrapInfo.WrappedAccessor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultClient_CreateToken_Role_InvalidToken(t *testing.T) {
|
||||
v := testutil.NewTestVault(t).Start()
|
||||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
defaultTestVaultRoleAndToken(v, t, 5)
|
||||
defaultTestVaultWhitelistRoleAndToken(v, t, 5)
|
||||
v.Config.Token = "foo-bar"
|
||||
|
||||
// Start the client
|
||||
|
@ -634,7 +855,7 @@ func TestVaultClient_CreateToken_Role_Unrecoverable(t *testing.T) {
|
|||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
v.Config.Token = defaultTestVaultRoleAndToken(v, t, 5)
|
||||
v.Config.Token = defaultTestVaultWhitelistRoleAndToken(v, t, 5)
|
||||
|
||||
// Start the client
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
@ -796,7 +1017,7 @@ func TestVaultClient_RevokeTokens_Role(t *testing.T) {
|
|||
defer v.Stop()
|
||||
|
||||
// Set the configs token in a new test role
|
||||
v.Config.Token = defaultTestVaultRoleAndToken(v, t, 5)
|
||||
v.Config.Token = defaultTestVaultWhitelistRoleAndToken(v, t, 5)
|
||||
|
||||
purged := 0
|
||||
purge := func(accessors []*structs.VaultAccessor) error {
|
||||
|
@ -846,16 +1067,15 @@ func TestVaultClient_RevokeTokens_Role(t *testing.T) {
|
|||
}
|
||||
|
||||
// Lookup the token and make sure we get an error
|
||||
if purged != 2 {
|
||||
t.Fatalf("Expected purged 2; got %d", purged)
|
||||
}
|
||||
if s, err := auth.Lookup(t1.Auth.ClientToken); err == nil {
|
||||
t.Fatalf("Revoked token lookup didn't fail: %+v", s)
|
||||
}
|
||||
if s, err := auth.Lookup(t2.Auth.ClientToken); err == nil {
|
||||
t.Fatalf("Revoked token lookup didn't fail: %+v", s)
|
||||
}
|
||||
|
||||
if purged != 2 {
|
||||
t.Fatalf("Expected purged 2; got %d", purged)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForConnection(v *vaultClient, t *testing.T) {
|
||||
|
|
|
@ -25,6 +25,21 @@ func (c *TokenAuth) Create(opts *TokenCreateRequest) (*Secret, error) {
|
|||
return ParseSecret(resp.Body)
|
||||
}
|
||||
|
||||
func (c *TokenAuth) CreateOrphan(opts *TokenCreateRequest) (*Secret, error) {
|
||||
r := c.c.NewRequest("POST", "/v1/auth/token/create-orphan")
|
||||
if err := r.SetJSONBody(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return ParseSecret(resp.Body)
|
||||
}
|
||||
|
||||
func (c *TokenAuth) CreateWithRole(opts *TokenCreateRequest, roleName string) (*Secret, error) {
|
||||
r := c.c.NewRequest("POST", "/v1/auth/token/create/"+roleName)
|
||||
if err := r.SetJSONBody(opts); err != nil {
|
||||
|
@ -41,7 +56,12 @@ func (c *TokenAuth) CreateWithRole(opts *TokenCreateRequest, roleName string) (*
|
|||
}
|
||||
|
||||
func (c *TokenAuth) Lookup(token string) (*Secret, error) {
|
||||
r := c.c.NewRequest("GET", "/v1/auth/token/lookup/"+token)
|
||||
r := c.c.NewRequest("POST", "/v1/auth/token/lookup")
|
||||
if err := r.SetJSONBody(map[string]interface{}{
|
||||
"token": token,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
|
@ -53,8 +73,12 @@ func (c *TokenAuth) Lookup(token string) (*Secret, error) {
|
|||
}
|
||||
|
||||
func (c *TokenAuth) LookupAccessor(accessor string) (*Secret, error) {
|
||||
r := c.c.NewRequest("POST", "/v1/auth/token/lookup-accessor/"+accessor)
|
||||
|
||||
r := c.c.NewRequest("POST", "/v1/auth/token/lookup-accessor")
|
||||
if err := r.SetJSONBody(map[string]interface{}{
|
||||
"accessor": accessor,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -77,10 +101,11 @@ func (c *TokenAuth) LookupSelf() (*Secret, error) {
|
|||
}
|
||||
|
||||
func (c *TokenAuth) Renew(token string, increment int) (*Secret, error) {
|
||||
r := c.c.NewRequest("PUT", "/v1/auth/token/renew/"+token)
|
||||
|
||||
body := map[string]interface{}{"increment": increment}
|
||||
if err := r.SetJSONBody(body); err != nil {
|
||||
r := c.c.NewRequest("PUT", "/v1/auth/token/renew")
|
||||
if err := r.SetJSONBody(map[string]interface{}{
|
||||
"token": token,
|
||||
"increment": increment,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -113,7 +138,12 @@ func (c *TokenAuth) RenewSelf(increment int) (*Secret, error) {
|
|||
// RevokeAccessor revokes a token associated with the given accessor
|
||||
// along with all the child tokens.
|
||||
func (c *TokenAuth) RevokeAccessor(accessor string) error {
|
||||
r := c.c.NewRequest("POST", "/v1/auth/token/revoke-accessor/"+accessor)
|
||||
r := c.c.NewRequest("POST", "/v1/auth/token/revoke-accessor")
|
||||
if err := r.SetJSONBody(map[string]interface{}{
|
||||
"accessor": accessor,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -126,7 +156,13 @@ func (c *TokenAuth) RevokeAccessor(accessor string) error {
|
|||
// RevokeOrphan revokes a token without revoking the tree underneath it (so
|
||||
// child tokens are orphaned rather than revoked)
|
||||
func (c *TokenAuth) RevokeOrphan(token string) error {
|
||||
r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-orphan/"+token)
|
||||
r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-orphan")
|
||||
if err := r.SetJSONBody(map[string]interface{}{
|
||||
"token": token,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -136,7 +172,9 @@ func (c *TokenAuth) RevokeOrphan(token string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// RevokeSelf revokes the token making the call
|
||||
// RevokeSelf revokes the token making the call. The `token` parameter is kept
|
||||
// for backwards compatibility but is ignored; only the client's set token has
|
||||
// an effect.
|
||||
func (c *TokenAuth) RevokeSelf(token string) error {
|
||||
r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-self")
|
||||
resp, err := c.c.RawRequest(r)
|
||||
|
@ -152,7 +190,13 @@ func (c *TokenAuth) RevokeSelf(token string) error {
|
|||
// the entire tree underneath -- all of its child tokens, their child tokens,
|
||||
// etc.
|
||||
func (c *TokenAuth) RevokeTree(token string) error {
|
||||
r := c.c.NewRequest("PUT", "/v1/auth/token/revoke/"+token)
|
||||
r := c.c.NewRequest("PUT", "/v1/auth/token/revoke")
|
||||
if err := r.SetJSONBody(map[string]interface{}{
|
||||
"token": token,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -48,7 +48,7 @@ type Config struct {
|
|||
redirectSetup sync.Once
|
||||
|
||||
// MaxRetries controls the maximum number of times to retry when a 5xx error
|
||||
// occurs. Set to 0 or less to disable retrying.
|
||||
// occurs. Set to 0 or less to disable retrying. Defaults to 0.
|
||||
MaxRetries int
|
||||
}
|
||||
|
||||
|
@ -99,12 +99,10 @@ func DefaultConfig() *Config {
|
|||
config.Address = v
|
||||
}
|
||||
|
||||
config.MaxRetries = pester.DefaultClient.MaxRetries
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// ConfigureTLS takes a set of TLS configurations and applies those to the HTTP client.
|
||||
// ConfigureTLS takes a set of TLS configurations and applies those to the the HTTP client.
|
||||
func (c *Config) ConfigureTLS(t *TLSConfig) error {
|
||||
|
||||
if c.HttpClient == nil {
|
||||
|
@ -289,6 +287,11 @@ func (c *Client) SetAddress(addr string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Address returns the Vault URL the client is configured to connect to
|
||||
func (c *Client) Address() string {
|
||||
return c.addr.String()
|
||||
}
|
||||
|
||||
// SetWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
|
||||
// for a given operation and path
|
||||
func (c *Client) SetWrappingLookupFunc(lookupFunc WrappingLookupFunc) {
|
||||
|
@ -327,17 +330,19 @@ func (c *Client) NewRequest(method, path string) *Request {
|
|||
Params: make(map[string][]string),
|
||||
}
|
||||
|
||||
var lookupPath string
|
||||
switch {
|
||||
case strings.HasPrefix(path, "/v1/"):
|
||||
lookupPath = strings.TrimPrefix(path, "/v1/")
|
||||
case strings.HasPrefix(path, "v1/"):
|
||||
lookupPath = strings.TrimPrefix(path, "v1/")
|
||||
default:
|
||||
lookupPath = path
|
||||
}
|
||||
if c.wrappingLookupFunc != nil {
|
||||
var lookupPath string
|
||||
switch {
|
||||
case strings.HasPrefix(path, "/v1/"):
|
||||
lookupPath = strings.TrimPrefix(path, "/v1/")
|
||||
case strings.HasPrefix(path, "v1/"):
|
||||
lookupPath = strings.TrimPrefix(path, "v1/")
|
||||
default:
|
||||
lookupPath = path
|
||||
}
|
||||
req.WrapTTL = c.wrappingLookupFunc(method, lookupPath)
|
||||
} else {
|
||||
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
|
||||
}
|
||||
|
||||
return req
|
||||
|
|
|
@ -3,6 +3,8 @@ package api
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
)
|
||||
|
@ -11,6 +13,26 @@ const (
|
|||
wrappedResponseLocation = "cubbyhole/response"
|
||||
)
|
||||
|
||||
var (
|
||||
// The default TTL that will be used with `sys/wrapping/wrap`, can be
|
||||
// changed
|
||||
DefaultWrappingTTL = "5m"
|
||||
|
||||
// The default function used if no other function is set, which honors the
|
||||
// env var and wraps `sys/wrapping/wrap`
|
||||
DefaultWrappingLookupFunc = func(operation, path string) string {
|
||||
if os.Getenv(EnvVaultWrapTTL) != "" {
|
||||
return os.Getenv(EnvVaultWrapTTL)
|
||||
}
|
||||
|
||||
if (operation == "PUT" || operation == "POST") && path == "sys/wrapping/wrap" {
|
||||
return DefaultWrappingTTL
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
)
|
||||
|
||||
// Logical is used to perform logical backend operations on Vault.
|
||||
type Logical struct {
|
||||
c *Client
|
||||
|
@ -38,7 +60,10 @@ func (c *Logical) Read(path string) (*Secret, error) {
|
|||
}
|
||||
|
||||
func (c *Logical) List(path string) (*Secret, error) {
|
||||
r := c.c.NewRequest("GET", "/v1/"+path)
|
||||
r := c.c.NewRequest("LIST", "/v1/"+path)
|
||||
// Set this for broader compatibility, but we use LIST above to be able to
|
||||
// handle the wrapping lookup function
|
||||
r.Method = "GET"
|
||||
r.Params.Set("list", "true")
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if resp != nil {
|
||||
|
@ -93,10 +118,48 @@ func (c *Logical) Delete(path string) (*Secret, error) {
|
|||
}
|
||||
|
||||
func (c *Logical) Unwrap(wrappingToken string) (*Secret, error) {
|
||||
origToken := c.c.Token()
|
||||
defer c.c.SetToken(origToken)
|
||||
var data map[string]interface{}
|
||||
if wrappingToken != "" {
|
||||
if c.c.Token() == "" {
|
||||
c.c.SetToken(wrappingToken)
|
||||
} else if wrappingToken != c.c.Token() {
|
||||
data = map[string]interface{}{
|
||||
"token": wrappingToken,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.c.SetToken(wrappingToken)
|
||||
r := c.c.NewRequest("PUT", "/v1/sys/wrapping/unwrap")
|
||||
if err := r.SetJSONBody(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.c.RawRequest(r)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode != 404 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK: // New method is supported
|
||||
return ParseSecret(resp.Body)
|
||||
case http.StatusNotFound: // Fall back to old method
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if wrappingToken != "" {
|
||||
origToken := c.c.Token()
|
||||
defer c.c.SetToken(origToken)
|
||||
c.c.SetToken(wrappingToken)
|
||||
}
|
||||
|
||||
secret, err := c.Read(wrappedResponseLocation)
|
||||
if err != nil {
|
||||
|
|
|
@ -62,6 +62,7 @@ type SSHHelperConfig struct {
|
|||
AllowedCidrList string `hcl:"allowed_cidr_list"`
|
||||
AllowedRoles string `hcl:"allowed_roles"`
|
||||
TLSSkipVerify bool `hcl:"tls_skip_verify"`
|
||||
TLSServerName string `hcl:"tls_server_name"`
|
||||
}
|
||||
|
||||
// SetTLSParameters sets the TLS parameters for this SSH agent.
|
||||
|
@ -70,6 +71,7 @@ func (c *SSHHelperConfig) SetTLSParameters(clientConfig *Config, certPool *x509.
|
|||
InsecureSkipVerify: c.TLSSkipVerify,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
RootCAs: certPool,
|
||||
ServerName: c.TLSServerName,
|
||||
}
|
||||
|
||||
transport := cleanhttp.DefaultTransport()
|
||||
|
@ -77,6 +79,16 @@ func (c *SSHHelperConfig) SetTLSParameters(clientConfig *Config, certPool *x509.
|
|||
clientConfig.HttpClient.Transport = transport
|
||||
}
|
||||
|
||||
// Returns true if any of the following conditions are true:
|
||||
// * CA cert is configured
|
||||
// * CA path is configured
|
||||
// * configured to skip certificate verification
|
||||
// * TLS server name is configured
|
||||
//
|
||||
func (c *SSHHelperConfig) shouldSetTLSParameters() bool {
|
||||
return c.CACert != "" || c.CAPath != "" || c.TLSServerName != "" || c.TLSSkipVerify
|
||||
}
|
||||
|
||||
// NewClient returns a new client for the configuration. This client will be used by the
|
||||
// vault-ssh-helper to communicate with Vault server and verify the OTP entered by user.
|
||||
// If the configuration supplies Vault SSL certificates, then the client will
|
||||
|
@ -89,7 +101,7 @@ func (c *SSHHelperConfig) NewClient() (*Client, error) {
|
|||
clientConfig.Address = c.VaultAddr
|
||||
|
||||
// Check if certificates are provided via config file.
|
||||
if c.CACert != "" || c.CAPath != "" || c.TLSSkipVerify {
|
||||
if c.shouldSetTLSParameters() {
|
||||
rootConfig := &rootcerts.Config{
|
||||
CAFile: c.CACert,
|
||||
CAPath: c.CAPath,
|
||||
|
@ -145,6 +157,7 @@ func ParseSSHHelperConfig(contents string) (*SSHHelperConfig, error) {
|
|||
"allowed_cidr_list",
|
||||
"allowed_roles",
|
||||
"tls_skip_verify",
|
||||
"tls_server_name",
|
||||
}
|
||||
if err := checkHCLKeys(list, valid); err != nil {
|
||||
return nil, multierror.Prefix(err, "ssh_helper:")
|
||||
|
|
|
@ -38,6 +38,7 @@ type InitRequest struct {
|
|||
RecoveryShares int `json:"recovery_shares"`
|
||||
RecoveryThreshold int `json:"recovery_threshold"`
|
||||
RecoveryPGPKeys []string `json:"recovery_pgp_keys"`
|
||||
RootTokenPGPKey string `json:"root_token_pgp_key"`
|
||||
}
|
||||
|
||||
type InitStatusResponse struct {
|
||||
|
|
|
@ -828,10 +828,10 @@
|
|||
"revisionTime": "2016-08-21T23:40:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "JH8wmQ8cWdn7mYu1T7gJ3IMIrec=",
|
||||
"checksumSHA1": "31yBeS6U3xm7VJ7ZvDxRgBxXP0A=",
|
||||
"path": "github.com/hashicorp/vault/api",
|
||||
"revision": "182ba68a9589d4cef95234134aaa498a686e3de3",
|
||||
"revisionTime": "2016-08-21T23:40:57Z"
|
||||
"revision": "f4adc7fa960ed8e828f94bc6785bcdbae8d1b263",
|
||||
"revisionTime": "2016-12-16T21:07:16Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "5lR6EdY0ARRdKAq3hZcL38STD8Q=",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Allow creating tokens under the role
|
||||
path "auth/token/create/nomad-server" {
|
||||
capabilities = ["create", "update"]
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# Allow looking up the role
|
||||
|
|
|
@ -47,6 +47,12 @@ vault {
|
|||
- `enabled` `(bool: false)` - Specifies if the Vault integration should be
|
||||
activated.
|
||||
|
||||
- `create_from_role` `(string: "")` - Specifies the role to create tokens from.
|
||||
The token given to Nomad does not have to be created from this role but must
|
||||
have "update" capability on "auth/token/create/<create_from_role>" path in
|
||||
Vault. If this value is unset and the token is created from a role, the value
|
||||
is defaulted to the role the token is from.
|
||||
|
||||
- `task_token_ttl` `(string: "")` - Specifies the TTL of created tokens when
|
||||
using a root token. This is specified using a label suffix like "30s" or "1h".
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ when creating the role with the Vault endpoint `/auth/token/roles/<role_name>`:
|
|||
```hcl
|
||||
# Allow creating tokens under the role
|
||||
path "auth/token/create/nomad-server" {
|
||||
capabilities = ["create", "update"]
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# Allow looking up the role
|
||||
|
|
Loading…
Reference in New Issue