From 47037a336f875794354a2e58d9134488f2479d86 Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Mon, 31 Oct 2016 22:43:51 -0700 Subject: [PATCH 1/5] Update vault_test to use minimal set of policies + start of Vault documentation --- nomad/vault_test.go | 153 +++++++++++++++++- .../docs/service-discovery/index.html.md | 3 - .../docs/vault-integration/index.html.md | 153 ++++++++++++++++++ website/source/layouts/docs.erb | 4 + 4 files changed, 303 insertions(+), 10 deletions(-) create mode 100644 website/source/docs/vault-integration/index.html.md diff --git a/nomad/vault_test.go b/nomad/vault_test.go index f60aa6eae..e7514a919 100644 --- a/nomad/vault_test.go +++ b/nomad/vault_test.go @@ -22,12 +22,20 @@ import ( const ( // authPolicy is a policy that allows token creation operations - authPolicy = `path "auth/token/create/*" { - capabilities = ["create", "read", "update", "delete", "list"] + authPolicy = `path "auth/token/create/test" { + capabilities = ["create", "update"] } -path "auth/token/roles/*" { - capabilities = ["create", "read", "update", "delete", "list"] +path "auth/token/lookup/*" { + capabilities = ["read"] +} + +path "auth/token/roles/test" { + capabilities = ["read"] +} + +path "/auth/token/revoke-accessor/*" { + capabilities = ["update"] } ` ) @@ -199,7 +207,7 @@ func TestVaultClient_SetConfig(t *testing.T) { // created in that role func defaultTestVaultRoleAndToken(v *testutil.TestVault, t *testing.T, rolePeriod int) string { d := make(map[string]interface{}, 2) - d["allowed_policies"] = "default,auth" + d["allowed_policies"] = "auth" d["period"] = rolePeriod return testVaultRoleAndToken(v, t, d) } @@ -312,7 +320,7 @@ func TestVaultClient_LookupToken_Invalid(t *testing.T) { } } -func TestVaultClient_LookupToken(t *testing.T) { +func TestVaultClient_LookupToken_Root(t *testing.T) { v := testutil.NewTestVault(t).Start() defer v.Stop() @@ -373,6 +381,70 @@ func TestVaultClient_LookupToken(t *testing.T) { } } +func TestVaultClient_LookupToken_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) + + 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) + + // Lookup ourselves + s, err := client.LookupToken(context.Background(), v.Config.Token) + if err != nil { + t.Fatalf("self lookup failed: %v", err) + } + + policies, err := PoliciesFrom(s) + if err != nil { + t.Fatalf("failed to parse policies: %v", err) + } + + expected := []string{"auth", "default"} + if !reflect.DeepEqual(policies, expected) { + t.Fatalf("Unexpected policies; got %v; want %v", policies, expected) + } + + // Create a token with a different set of policies + expected = []string{"default"} + req := vapi.TokenCreateRequest{ + Policies: expected, + } + s, err = v.Client.Auth().Token().Create(&req) + 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) + } + + // Lookup new child + s, err = client.LookupToken(context.Background(), s.Auth.ClientToken) + if err != nil { + t.Fatalf("self lookup failed: %v", err) + } + + policies, err = PoliciesFrom(s) + if err != nil { + t.Fatalf("failed to parse policies: %v", err) + } + + if !reflect.DeepEqual(policies, expected) { + t.Fatalf("Unexpected policies; got %v; want %v", policies, expected) + } +} + func TestVaultClient_LookupToken_RateLimit(t *testing.T) { v := testutil.NewTestVault(t).Start() defer v.Stop() @@ -621,7 +693,7 @@ func TestVaultClient_RevokeTokens_PreEstablishs(t *testing.T) { } } -func TestVaultClient_RevokeTokens(t *testing.T) { +func TestVaultClient_RevokeTokens_Root(t *testing.T) { v := testutil.NewTestVault(t).Start() defer v.Stop() @@ -685,6 +757,73 @@ func TestVaultClient_RevokeTokens(t *testing.T) { } } +func TestVaultClient_RevokeTokens_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) + + purged := 0 + purge := func(accessors []*structs.VaultAccessor) error { + purged += len(accessors) + return nil + } + + logger := log.New(os.Stderr, "", log.LstdFlags) + client, err := NewVaultClient(v.Config, logger, purge) + if err != nil { + t.Fatalf("failed to build vault client: %v", err) + } + client.SetActive(true) + defer client.Stop() + + waitForConnection(client, t) + + // Create some vault tokens + auth := v.Client.Auth().Token() + req := vapi.TokenCreateRequest{ + Policies: []string{"default"}, + } + t1, err := auth.Create(&req) + if err != nil { + t.Fatalf("Failed to create vault token: %v", err) + } + if t1 == nil || t1.Auth == nil { + t.Fatalf("bad secret response: %+v", t1) + } + t2, err := auth.Create(&req) + if err != nil { + t.Fatalf("Failed to create vault token: %v", err) + } + if t2 == nil || t2.Auth == nil { + t.Fatalf("bad secret response: %+v", t2) + } + + // Create two VaultAccessors + vas := []*structs.VaultAccessor{ + &structs.VaultAccessor{Accessor: t1.Auth.Accessor}, + &structs.VaultAccessor{Accessor: t2.Auth.Accessor}, + } + + // Issue a token revocation + if err := client.RevokeTokens(context.Background(), vas, true); err != nil { + t.Fatalf("RevokeTokens failed: %v", err) + } + + // Lookup the token and make sure we get an error + 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) { testutil.WaitForResult(func() (bool, error) { return v.ConnectionEstablished() diff --git a/website/source/docs/service-discovery/index.html.md b/website/source/docs/service-discovery/index.html.md index ba6b652ca..493c84ddc 100644 --- a/website/source/docs/service-discovery/index.html.md +++ b/website/source/docs/service-discovery/index.html.md @@ -39,9 +39,6 @@ To configure a job to register with service discovery, please see the - The service discovery feature in Nomad depends on operators making sure that the Nomad client can reach the Consul agent. -- Nomad assumes that it controls the life cycle of all the externally - discoverable services running on a host. - - Tasks running inside Nomad also need to reach out to the Consul agent if they want to use any of the Consul APIs. Ex: A task running inside a docker container in the bridge mode won't be able to talk to a Consul Agent running diff --git a/website/source/docs/vault-integration/index.html.md b/website/source/docs/vault-integration/index.html.md new file mode 100644 index 000000000..1dece7d85 --- /dev/null +++ b/website/source/docs/vault-integration/index.html.md @@ -0,0 +1,153 @@ +--- +layout: "docs" +page_title: "Vault Integration" +sidebar_current: "docs-vault-integration" +description: |- + Learn how to integrate with HashiCorp Vault and retrieve Vault tokens for + tasks. +--- + +# Vault Integration + +Many workloads require access to tokens, passwords, certificates, API keys, and +other secrets. To enable secure, auditable and easy access to your secrets, +Nomad integrates with HashiCorp's [Vault][]. Nomad Servers and Clients +coordinate with Vault to derive a Vault token that has access to only the Vault +policies the tasks needs. Nomad Clients make the token avaliable to the task and +handle the tokens renewal. Further, Nomad's [`template` block][template] can +retrieve secrets from Vault making it easier than ever to secure your +infrastructure. + +Note that in order to use Vault with Nomad, you will need to configure and +install Vault separately from Nomad. Nomad does not run Vault for you. + +## Vault Configuration + +In order to use the Vault integration, Nomad Servers must be given a Vault +token. This Vault token can be either a root token or a token created from a +role. The root token provides an easy way to get started but it is recommended +to use the role based token described below. If the token is periodic, Nomad +Servers will renew the token. + +### Root Token + +If Nomad is given a root token, no further configuration is needed as Nomad can +derive a token for jobs using any Vault policies. + +### Role based Token + +Vault's [Token Authentication Backend][auth] supports a concept called "roles". +Roles allow policies to be grouped together and token creation to be delegated +to a trusted service such as Nomad. By creating a role, the set of policies that +task's managed by Nomad can acess may be limited compared to giving Nomad a root +token. + +When given a non-root token, Nomad queries the token to determine the role it +was generated from. It will then derive tokens for jobs based on that role. +Nomad expects the role to be created with several properties described below +when creating the role with the Vault endpoint `/auth/token/roles/`: + +```json +{ + "allowed_policies": "", + "explicit_max_ttl": 0, + "name": "nomad", + "orphan": false, + "period": 259200, + "renewable": true +} +``` + +#### Parameters: + +* `allowed_policies`: The `allowed_policies` is a comma separated list of + policies. This list should contain all policies that jobs running under Nomad + should have access to. Further, the list must contain one or more policies + that gives Nomad the following permissions: + + ``` + # Allow creating tokens under the role + path "auth/token/create/" { + capabilities = ["create", "update"] + } + + # Allow looking up the role + path "auth/token/roles/" { + capabilities = ["read"] + } + + # Allow looking up incoming tokens to validate they have permissions to + # access the tokens they are requesting + path "auth/token/lookup/*" { + capabilities = ["read"] + } + + # Allow revoking tokens that should no longer exist + path "/auth/token/revoke-accessor/*" { + capabilities = ["update"] + } + ``` + +* `explicit_max_ttl`: Must be set to `0` to allow periodic tokens. + +* `name`: Any name is acceptable. + +* `orphan`: Must be set to `false`. This ensures that the token can be revoked + when the task is no longer needed or a node dies. This prohibits a leaked + token being used past the lifetime of a task. + +* `period`: Must be set to a positive value. The period specifies the length the + TTL is extended by each renewal in seconds. It is suggested to set this value + on the order of magniture of 3 days (259200 seconds) to avoid a large renewal + request rate to Vault. + +* `renewable`: Must be set to `true`. This is to allow Nomad to renew tokens for + tasks. + +See Vault's [Token Authentication Backend][auth] documentation for all possible +fields and more complete documentation. + +#### Retrieving the Role based Token + +After the role is created, a token suitable for the Nomad servers may be +retrieved by issuing the following Vault command: + +``` +$ vault token-create -role +Key Value +--- ----- +token f02f01c2-c0d1-7cb7-6b88-8a14fada58c0 +token_accessor 8cb7fcb3-9a4f-6fbf-0efc-83092bb0cb1c +token_duration 259200s +token_renewable true +token_policies [] +``` + +The token can then be set in the Server configuration's [vault block][config] or +as a command-line flag: + +``` +$ nomad agent -config /path/to/config -vault-token=f02f01c2-c0d1-7cb7-6b88-8a14fada58c0 +``` + +## Agent Configuration + +To enable Vault integration, please see the [Nomad agent Vault +integration][config] configuration. + +## Vault Definition Syntax + +To configure a job to retrieve Vault tokens, please see the [`vault` job +specification documentation][vault-spec]. + +## Assumptions + +- Vault 0.6.2 or later is needed. + +- Nomad is given either a root token or a token created from an approriate role. + +[auth]: https://www.vaultproject.io/docs/auth/token.html "Vault Authentication Backend" +[config]: /docs/agent/config.html#vault_options "Nomad Vault configuration block" +[template]: /docs/job-specification/template.html "Nomad template Job Specification" +[vault]: https://www.vaultproject.io/ "Vault by HashiCorp" +[vault-spec]: /docs/job-specification/vault.html "Nomad Vault Job Specification" diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index bdcc277c4..331988ccf 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -100,6 +100,10 @@ Service Discovery + > + Vault Integration + + > Operating a Job