From 49eade3e137b02685857ca9bd0ee6bb418fdca1c Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 1 Nov 2016 08:56:34 -0400 Subject: [PATCH 1/7] Clarify restart mode "fail" Fixes GH-1885 --- website/source/docs/job-specification/restart.html.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/job-specification/restart.html.md b/website/source/docs/job-specification/restart.html.md index 09230cf73..13e694a40 100644 --- a/website/source/docs/job-specification/restart.html.md +++ b/website/source/docs/job-specification/restart.html.md @@ -93,5 +93,5 @@ restart { `interval` is reached. This is the default behavior. - `"fail"` - Instructs the scheduler to not attempt to restart the task on - failure. This mode is useful for non-idempotent jobs which are not safe to - simply restart. + failure. This mode is useful for non-idempotent jobs which are unlikely to + succeed after a few failures. From b84d8212ffb2fb45ed335a7e58281c733be2fad1 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Tue, 1 Nov 2016 10:16:39 -0700 Subject: [PATCH 2/7] Add LXC docs and enable by default for lxc builds --- client/driver/lxc.go | 2 +- client/driver/lxc_test.go | 8 +- website/source/docs/drivers/lxc.html.md | 100 ++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 website/source/docs/drivers/lxc.html.md diff --git a/client/driver/lxc.go b/client/driver/lxc.go index ffcbc3897..57e78ca5d 100644 --- a/client/driver/lxc.go +++ b/client/driver/lxc.go @@ -155,7 +155,7 @@ func (d *LxcDriver) Abilities() DriverAbilities { // Fingerprint fingerprints the lxc driver configuration func (d *LxcDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { - enabled := cfg.ReadBoolDefault(lxcConfigOption, false) + enabled := cfg.ReadBoolDefault(lxcConfigOption, true) if !enabled && !cfg.DevMode { return false, nil } diff --git a/client/driver/lxc_test.go b/client/driver/lxc_test.go index 8a46ec46f..8cb47845d 100644 --- a/client/driver/lxc_test.go +++ b/client/driver/lxc_test.go @@ -35,16 +35,16 @@ func TestLxcDriver_Fingerprint(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - if apply { - t.Fatalf("should not apply by default") + if !apply { + t.Fatalf("should apply by default") } - apply, err = d.Fingerprint(&config.Config{Options: map[string]string{lxcConfigOption: "1"}}, node) + apply, err = d.Fingerprint(&config.Config{Options: map[string]string{lxcConfigOption: "0"}}, node) if err != nil { t.Fatalf("err: %v", err) } if !apply { - t.Fatalf("should apply with config") + t.Fatalf("should not apply with config") } if node.Attributes["driver.lxc"] == "" { t.Fatalf("missing driver") diff --git a/website/source/docs/drivers/lxc.html.md b/website/source/docs/drivers/lxc.html.md new file mode 100644 index 000000000..a34754769 --- /dev/null +++ b/website/source/docs/drivers/lxc.html.md @@ -0,0 +1,100 @@ +--- +layout: "docs" +page_title: "Drivers: LXC" +sidebar_current: "docs-drivers-lxc" +description: |- + The lxc task driver is used to run application containers using lxc. +--- + +# LXC Driver + +Name: `lxc` + +The `lxc` driver provides an interface for using LXC for running application +containers. + +!> **Experimental!** Currently, the LXC driver supports launching containers +via templates but only supports host networking. If both an LXC image and the +host it is run on use upstart or systemd, shutdown signals may be passed from +the container to the host. + +~> LXC is only enabled in the special `linux_amd64_lxc` build of Nomad because +it links to the `liblxc` system library. Use the `lxc` build tag if compiling +Nomad yourself. + +## Task Configuration + +```hcl +task "busybox" { + driver = "lxc" + + config { + log_level = "trace" + verbosity = "verbose" + template = "/usr/share/lxc/templates/lxc-busybox" + } +} +``` + +The `lxc` driver supports the following configuration in the job spec: + +* `template` - The LXC template to run. + + ```hcl + config { + template = "/usr/share/lxc/templates/lxc-alpine" + } + ``` + +* `log_level` - (Optional) LXC library's logging level. Defaults to `error`. + Must be one of `trace`, `debug`, `info`, `warn`, or `error`. + + ```hcl + config { + log_level = "debug" + } + ``` + +* `verbosity` - (Optional) Enables extra verbosity in the LXC library's + logging. Defaults to `quiet`. Must be one of `quiet` or `verbose`. + + ```hcl + config { + verbosity = "quiet" + } + ``` + +## Networking + +Currently the `lxc` driver only supports host networking. See the `none` +networking type in the [`lxc.container.conf` manual][lxc_man] for more +information. + +[lxc_man]: https://linuxcontainers.org/lxc/manpages/man5/lxc.container.conf.5.html#lbAM + +## Client Requirements + +The `lxc` driver requires the following: + +* 64bit Linux host +* The `linux_amd64_lxc` Nomad binary +* `liblxc` to be installed +* `lxc-templates` to be installed + +## Client Configuration + +* `lxc.enable` - The `lxc` driver may be disabled on hosts by setting this + [client configuration][/docs/agent/config.html#options] option to `false` + (defaults to `true`). + +## Client Attributes + +The `lxc` driver will set the following client attributes: + +* `driver.lxc` - Set to `1` if LXC is found and enabled on the host node. +* `driver.lxc.version` - Version of `lxc` eg: `1.1.0`. + +## Resource Isolation + +This driver supports CPU and memory isolation via the `lxc` library. Network +isolation is not supported as of now. From 8f365bfbbfda6bcfd9d01276172049c0bb10d26c Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Mon, 31 Oct 2016 22:43:51 -0700 Subject: [PATCH 3/7] 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