From c072c0dfbb0a002eca9292e693811dd7cd0b0e2a Mon Sep 17 00:00:00 2001 From: Max Englander Date: Thu, 11 Aug 2016 22:22:41 -0400 Subject: [PATCH] #7013 add tls config support to consul provider (#7015) * #7013 add tls config support to consul provider * #7013 add acceptance tests * #7013 use GFM tables * #7013 require one of {CONSUL_ADDRESS,CONSUL_HTTP_ADDR} when running consul acc tests --- builtin/providers/consul/config.go | 18 +- builtin/providers/consul/resource_provider.go | 27 ++- .../consul/resource_provider_test.go | 40 +++-- .../providers/consul/test-fixtures/README.md | 41 +++++ .../consul/test-fixtures/agent.json.example | 11 ++ .../consul/test-fixtures/agentcert.pem | 27 +++ .../consul/test-fixtures/agentkey.pem | 27 +++ .../providers/consul/test-fixtures/cacert.pem | 22 +++ .../consul/test-fixtures/usercert.pem | 25 +++ .../consul/test-fixtures/userkey.pem | 27 +++ .../github.com/hashicorp/consul/api/agent.go | 107 +++++++++--- vendor/github.com/hashicorp/consul/api/api.go | 113 ++++++++++++- .../hashicorp/consul/api/catalog.go | 15 +- .../github.com/hashicorp/consul/api/health.go | 22 ++- vendor/github.com/hashicorp/consul/api/kv.go | 156 ++++++++++++++++++ vendor/vendor.json | 4 +- .../docs/providers/consul/index.html.markdown | 4 + 17 files changed, 633 insertions(+), 53 deletions(-) create mode 100644 builtin/providers/consul/test-fixtures/README.md create mode 100644 builtin/providers/consul/test-fixtures/agent.json.example create mode 100644 builtin/providers/consul/test-fixtures/agentcert.pem create mode 100644 builtin/providers/consul/test-fixtures/agentkey.pem create mode 100644 builtin/providers/consul/test-fixtures/cacert.pem create mode 100644 builtin/providers/consul/test-fixtures/usercert.pem create mode 100644 builtin/providers/consul/test-fixtures/userkey.pem diff --git a/builtin/providers/consul/config.go b/builtin/providers/consul/config.go index cb6d7af79..c048b5dda 100644 --- a/builtin/providers/consul/config.go +++ b/builtin/providers/consul/config.go @@ -2,6 +2,7 @@ package consul import ( "log" + "net/http" consulapi "github.com/hashicorp/consul/api" ) @@ -9,8 +10,11 @@ import ( type Config struct { Datacenter string `mapstructure:"datacenter"` Address string `mapstructure:"address"` - Token string `mapstructure:"token"` Scheme string `mapstructure:"scheme"` + Token string `mapstructure:"token"` + CAFile string `mapstructure:"ca_file"` + CertFile string `mapstructure:"cert_file"` + KeyFile string `mapstructure:"key_file"` } // Client() returns a new client for accessing consul. @@ -26,9 +30,21 @@ func (c *Config) Client() (*consulapi.Client, error) { if c.Scheme != "" { config.Scheme = c.Scheme } + + tlsConfig := &consulapi.TLSConfig{} + tlsConfig.CAFile = c.CAFile + tlsConfig.CertFile = c.CertFile + tlsConfig.KeyFile = c.KeyFile + cc, err := consulapi.SetupTLSConfig(tlsConfig) + if err != nil { + return nil, err + } + config.HttpClient.Transport.(*http.Transport).TLSClientConfig = cc + if c.Token != "" { config.Token = c.Token } + client, err := consulapi.NewClient(config) log.Printf("[INFO] Consul Client configured with address: '%s', scheme: '%s', datacenter: '%s'", diff --git a/builtin/providers/consul/resource_provider.go b/builtin/providers/consul/resource_provider.go index 9b15ecdab..c6e3c5b8a 100644 --- a/builtin/providers/consul/resource_provider.go +++ b/builtin/providers/consul/resource_provider.go @@ -20,11 +20,34 @@ func Provider() terraform.ResourceProvider { "address": &schema.Schema{ Type: schema.TypeString, Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "CONSUL_ADDRESS", + "CONSUL_HTTP_ADDR", + }, nil), }, "scheme": &schema.Schema{ - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("CONSUL_SCHEME", nil), + }, + + "ca_file": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("CONSUL_CA_FILE", nil), + }, + + "cert_file": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("CONSUL_CERT_FILE", nil), + }, + + "key_file": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("CONSUL_KEY_FILE", nil), }, "token": &schema.Schema{ diff --git a/builtin/providers/consul/resource_provider_test.go b/builtin/providers/consul/resource_provider_test.go index eb7f73ba0..df8fe1b85 100644 --- a/builtin/providers/consul/resource_provider_test.go +++ b/builtin/providers/consul/resource_provider_test.go @@ -4,7 +4,6 @@ import ( "os" "testing" - consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" @@ -18,12 +17,6 @@ func init() { testAccProviders = map[string]terraform.ResourceProvider{ "consul": testAccProvider, } - - // Use the demo address for the acceptance tests - testAccProvider.ConfigureFunc = func(d *schema.ResourceData) (interface{}, error) { - conf := consulapi.DefaultConfig() - return consulapi.NewClient(conf) - } } func TestResourceProvider(t *testing.T) { @@ -56,8 +49,35 @@ func TestResourceProvider_Configure(t *testing.T) { } } -func testAccPreCheck(t *testing.T) { - if v := os.Getenv("CONSUL_HTTP_ADDR"); v == "" { - t.Fatal("CONSUL_HTTP_ADDR must be set for acceptance tests") +func TestResourceProvider_ConfigureTLS(t *testing.T) { + rp := Provider() + + raw := map[string]interface{}{ + "address": "demo.consul.io:80", + "ca_file": "test-fixtures/cacert.pem", + "cert_file": "test-fixtures/usercert.pem", + "datacenter": "nyc3", + "key_file": "test-fixtures/userkey.pem", + "scheme": "https", + } + + rawConfig, err := config.NewRawConfig(raw) + if err != nil { + t.Fatalf("err: %s", err) + } + + err = rp.Configure(terraform.NewResourceConfig(rawConfig)) + if err != nil { + t.Fatalf("err: %s", err) } } + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("CONSUL_HTTP_ADDR"); v != "" { + return + } + if v := os.Getenv("CONSUL_ADDRESS"); v != "" { + return + } + t.Fatal("Either CONSUL_ADDRESS or CONSUL_HTTP_ADDR must be set for acceptance tests") +} diff --git a/builtin/providers/consul/test-fixtures/README.md b/builtin/providers/consul/test-fixtures/README.md new file mode 100644 index 000000000..91cdc1248 --- /dev/null +++ b/builtin/providers/consul/test-fixtures/README.md @@ -0,0 +1,41 @@ +# Running Consul for Terraform Acceptance Tests + +## TLS + +Some of the acceptance tests for the `consul` provider use +TLS. To service these tests, a Consul server must be started +with HTTPS enabled with TLS certificates. + +### Test fixtures + +File | Description +--- | --- +`agent.json.example` | Configures the Consul agent to respond to HTTPS requests, and verifies the authenticity of HTTPS requests +`agentcert.pem` | A PEM-encoded certificate used by the Consul agent, valid only for 127.0.0.1 signed by `cacert.pem`, expires 2026 +`agentkey.pem` | A PEM-encoded private key used by the Consul agent +`cacert.pem` | A PEM-encoded Certificate Authority, expires 2036 +`usercert.pem` | A PEM-encoded certificate used by the Terraform acceptance tests, signed by `cacert.pem`, expires 2026 +`userkey.pem` | A PEM-encoded private key used by the Terraform acceptance tests + +### Start + +Start a Consul server configured to serve HTTP traffic, and validate incoming +HTTPS requests. + + ~/.go/src/github.com/hashicorp/terraform> consul agent \ + -bind 127.0.0.1 \ + -data-dir=/tmp \ + -dev \ + -config-file=builtin/providers/consul/text-fixtures/agent.json.example \ + -server + +### Test + +With TLS, `CONSUL_HTTP_ADDR` must match the Common Name of the agent certificate. + + ~/.go/src/github.com/hashicorp/terraform> CONSUL_CERT_FILE=test-fixtures/usercert.pem \ + CONSUL_KEY_FILE=test-fixtures/userkey.pem \ + CONSUL_CA_FILE=test-fixtures/cacert.pem \ + CONSUL_SCHEME=https \ + CONSUL_HTTP_ADDR=127.0.0.1:8943 \ + make testacc TEST=./builtin/providers/consul/ diff --git a/builtin/providers/consul/test-fixtures/agent.json.example b/builtin/providers/consul/test-fixtures/agent.json.example new file mode 100644 index 000000000..aefe437a7 --- /dev/null +++ b/builtin/providers/consul/test-fixtures/agent.json.example @@ -0,0 +1,11 @@ +{ + "ca_file": "./cacert.pem", + "cert_file": "./agentcert.pem", + "datacenter": "dc1", + "domain": "hashicorp.test", + "key_file": "./agentkey.pem", + "ports": { + "https": 8943 + }, + "verify_incoming": true +} diff --git a/builtin/providers/consul/test-fixtures/agentcert.pem b/builtin/providers/consul/test-fixtures/agentcert.pem new file mode 100644 index 000000000..b1b904e95 --- /dev/null +++ b/builtin/providers/consul/test-fixtures/agentcert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjjCCA3agAwIBAgICEAMwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UE +CwwJSGFzaGlDb3JwMRowGAYDVQQDDBFIYXNoaUNvcnAgVGVzdCBDQTAgFw0xNjA4 +MTEwNTEwMDRaGA8yMDY2MDczMDA1MTAwNFowXjELMAkGA1UEBhMCVVMxEzARBgNV +BAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UECwwJSGFz +aGlDb3JwMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDQiR2zwLdfCd95LIrKekZlDKo3YF5nMZFzR3OR1Mc8jCAaYLz/ +ZFr8hVSAsygwZ+tHzoHP0U3FxeYemPtjLAPE077C6h+v6pHiTLxOkd22GtyalgMZ +E4ACGSogqDUvwssxxDUsG2ItzhVCB0GXTlfo/6XhApyRqvnEto+ZJ+zk6MiHnAmc +eN9sx0c5K097+Nq7PZgtk6HOxbKSvMWEkTtHrOBrhc9lTfwVSiWHdZ2X0wpOL1Ra +pFnBMDxnWyH2ivVMknarzKz2pBBDJwTGvsJcC1ymprqU+SRyjs75BfNv2BKJrhb4 +vBj3YEGMBEhHKtnObniGqV8W4o9jBIwocFpfAgMBAAGjggFKMIIBRjAPBgNVHREE +CDAGhwR/AAABMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG ++EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYD +VR0OBBYEFNzoTM6XceaITc2lVIDrXaYBKJolMIGRBgNVHSMEgYkwgYaAFANEnP7/ +5Iil24eKuYJTt/IJPfamoWqkaDBmMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2Fs +aWZvcm5pYTESMBAGA1UECgwJSGFzaGlDb3JwMRIwEAYDVQQLDAlIYXNoaUNvcnAx +GjAYBgNVBAMMEUhhc2hpQ29ycCBUZXN0IENBggIQADAOBgNVHQ8BAf8EBAMCBaAw +HQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IB +AQB4p5sWE+p+sheXYgkg/PsATRMxYDPRTw0Zvd2AKOrodqF4IlibSb4mVVh0fPtx +2VX3z/WOZb8wgXNnEUhVcijf7LgGvw/SvQGgW5mXYSCcHeb4ETFJ1yuKZj5yn5tl +vZx1Sq/fGFkjHn3mgL+gzyQlNk1Wt0p3fLsIfpMOgpntSdRq3IUvf+W+oH5BUrTl +WgaXUD3lkdx3R9h3uLX4nxJWpMPViPCpr3ADW9oEwoHHQbe3LC7iJI2Us/qIH73n +Du7mUk+/HSkajjFsxnVoFCF1+RMqf1i9w7tXaAwWYT+vaP46fq3M/Bmsv/gDc5ur +8p48hpQ61Sfj0oU38Ftzzcs+ +-----END CERTIFICATE----- diff --git a/builtin/providers/consul/test-fixtures/agentkey.pem b/builtin/providers/consul/test-fixtures/agentkey.pem new file mode 100644 index 000000000..61e19a191 --- /dev/null +++ b/builtin/providers/consul/test-fixtures/agentkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA0Ikds8C3XwnfeSyKynpGZQyqN2BeZzGRc0dzkdTHPIwgGmC8 +/2Ra/IVUgLMoMGfrR86Bz9FNxcXmHpj7YywDxNO+wuofr+qR4ky8TpHdthrcmpYD +GROAAhkqIKg1L8LLMcQ1LBtiLc4VQgdBl05X6P+l4QKckar5xLaPmSfs5OjIh5wJ +nHjfbMdHOStPe/jauz2YLZOhzsWykrzFhJE7R6zga4XPZU38FUolh3Wdl9MKTi9U +WqRZwTA8Z1sh9or1TJJ2q8ys9qQQQycExr7CXAtcpqa6lPkkco7O+QXzb9gSia4W ++LwY92BBjARIRyrZzm54hqlfFuKPYwSMKHBaXwIDAQABAoIBAFQxyAg3GtIITm3C +ChdN3vYVcvQAuJy5apw8kPCkE/ziJmQAAs6qWgHyYvfDXcqNanUHb2nUe64KBKr9 +4SFdN/hT9YUEud5wuo2/pZejVPydQ8w2HPIW6WvvdQ7SWwb5gsiJC17Pf4g22GZc +P6MzQlMURIjgYQ5/FXDStI+FiyOwDhHDbLoMaIOfToWJupd+sGREccSKOVmJdGY/ +7/n1AGvfbgUToy2sMEz7HqTtgRJW/Knko2dD3ZWh7KqFS2GUYsJ3Ake1CG7xT6sA +4MWQvfR/+t7xSpDDU2WlNgFi9sQ8UjrIhaMYaiFhV6h2bTVeGl1tvBmbE77Z1Lne +jcobwKECgYEA6SuDF0wuu8WnDXnCpNrd83gONy1pEp9s7vbf/GrMXGaavQmqb6x1 +sLZTXho1srqRLXGSAvbDS/yO6wRUd/CdLB8WBda3lcH9y/II1BDynEygGpipGa83 +7Ti+D2hMSMLhX1vsUcCwz3fz61wzBqvdvrjdymmivPLu3rMINd8twl0CgYEA5PQi +jwi483icPPOc1S/5CsKnj6nJwKVQz9dDeT6QLVDCoVh5u0X9WZAAnMdrR9yZS6ax +ZAF453DPlK6Ct7vcBPEFC1W6QrRhjJrzvRb/PLnzaRMY+YoEg2qmGreb+30jrx+4 +jkTLkz4Qag+jOdR3t4104Pix1CkpQUX0chD5u+sCgYAiuI8Bxh9jaLBSimIYqFrK +qYL8Zm+yDTlscCi0brbVv5WlNq5BiN3RnaTWa3K5lZyOts22UUaNpyMlDfUCEztk +WZCu9+VIkKWZXAZChe+KpMJmk3sCzxu14HA03SQW5aYnzAlptxbdHhCdaJJUmP0h +LGgifw5zsn0tfl1noD8xJQKBgBKSSwtXJcl6CxJWoG4aihT5XSYmG5tozXlOeMao +8ID8gA0eZCFwt/A/4gzVkDowBq9AQjteczQyzmO9FBVbQ6mS81nMBmPKxe7l0seP +yfxfCQOI7QmwzFTsnbSlGB36NJ7L7+h6ZBj5e9NemVrjhSJ6cvSct7AB9rq4te9a +uScpAoGBAOIjcv2lQsJ3GWBTHWCh23jC/0XPE4bJg9DjliHQDAB/Yp49oV1giWs6 +xI0SBsovtJqJxOd6F8e6HuQTt1X1kQ8Q1Itb78Wx9Rs4bvN51pxj4L+DTxLBMl5g +xXsS+Zfm5O2HGxU5t60CsxRyF0EVNVEtgKkIiQE+ZaQ1d0WJC8RI +-----END RSA PRIVATE KEY----- diff --git a/builtin/providers/consul/test-fixtures/cacert.pem b/builtin/providers/consul/test-fixtures/cacert.pem new file mode 100644 index 000000000..21aca8283 --- /dev/null +++ b/builtin/providers/consul/test-fixtures/cacert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrTCCApWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UE +CwwJSGFzaGlDb3JwMRowGAYDVQQDDBFIYXNoaUNvcnAgVGVzdCBDQTAgFw0xNjA4 +MTEwNTA1MDZaGA8yMTE2MDcxODA1MDUwNlowZjELMAkGA1UEBhMCVVMxEzARBgNV +BAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UECwwJSGFz +aGlDb3JwMRowGAYDVQQDDBFIYXNoaUNvcnAgVGVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKglYmf9Tv1u6e1ulQZNpmvUHi+D+/PBHyg9Ft60 +HiZaeBGyNPZX9uVuM1jN3o/qpwBQxhq3ojQafU3WDSU6Y0GZ1e8AcLsObeNUjma4 +eLjZy+059Vt7DKcp6LA+7JqELToK83QzqNdYuocze5v9hPt5W6Q3Dp5rsmVjOFim +6LxcN/TAcmW+ZrykOGOT4QyYFkamp4uMJkpX1UwO3djdQF7CllnOboUUYqGyOt9e +BBudhCsSvWpJa/wNcAH2AxzaIVu85Dmg3G0Erekcget5ewebsnhGs3emfWO/XQht +uwKdz60mz1vAIK3UR5eYCbxnLrXM0WfcYKFqhuQpqqONWtUCAwEAAaNjMGEwHQYD +VR0OBBYEFANEnP7/5Iil24eKuYJTt/IJPfamMB8GA1UdIwQYMBaAFANEnP7/5Iil +24eKuYJTt/IJPfamMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBCwUAA4IBAQBzzvX4GiBalxuW5YxMiuFAljNB+tW20Frz0s7bq0+Z +1+ErQIW26NUHH14RUU4vbisX09QMm4p62oJOpo/5nW1VqsyoTCQJXaolGF6UidFy +l/2bgOy8QbCOqrS0jt0MFQFDr9Z/m8dBgbjFzv8gfsnpxDQvi+iKkVSuzlIfcvoo +xlwtNnrD9lSsinP4Zo8sNqjhaRbih8zhsUdd0mUDDGczw2mY2CdMmeH0wflJMEVe +3hwR8650sCJlJfVuFUDsqy1K9T5j5NVv7i6RloeMvYOH2nwpIejE88lmjpXR6Bzw +g8geEjKOLBN8Nmak3jSvH2IewczZKSaKNSiv/4Izut/8 +-----END CERTIFICATE----- diff --git a/builtin/providers/consul/test-fixtures/usercert.pem b/builtin/providers/consul/test-fixtures/usercert.pem new file mode 100644 index 000000000..0580e364f --- /dev/null +++ b/builtin/providers/consul/test-fixtures/usercert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEJjCCAw6gAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UE +CwwJSGFzaGlDb3JwMRowGAYDVQQDDBFIYXNoaUNvcnAgVGVzdCBDQTAgFw0xNjA4 +MTEwNTA1MzFaGA8yMDY2MDczMDA1MDUzMVowcjELMAkGA1UEBhMCVVMxEzARBgNV +BAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCUhhc2hpQ29ycDESMBAGA1UECwwJSGFz +aGlDb3JwMSYwJAYDVQQDDB1IYXNoaUNvcnAgVGVzdCBUZXJyYWZvcm0gVXNlcjCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANrfzsj+gIM3pvuI+hdx2W5s +hTh2YXd4Q7cByLDzXPRgI0W1BFOIxOAdHy/zqxCKQPxiibxPqDCxzPnc7mSco8e0 +zvihAysthiUmWcNdF1pIh6631SU9rE+Mis6XcW2beuh/IVloXBwI4dmSuX3Urb0D +Aw3Rb5kCJzXUTBG/g8KriR6KyNFTu0Wb/1NcrrCnNAteQmpDuuMtx75stfoMUnlr +xZfsCZXHVpe8GmVlwqr8Mw7NKmyeKgl0rH1Mef6+ce9BPnVBxdJMEYWl+UQfTSV+ +pWoNtQTZxEbhbMFhYi410EJ5s0Nw6lyUnXrQ2/YglikIvnyfWj/CwLTZwaXlgAkC +AwEAAaOBzzCBzDAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgB +hvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0G +A1UdDgQWBBRIgDbi1wLtW+u3PgrbVrNDkGfwGjAfBgNVHSMEGDAWgBQDRJz+/+SI +pduHirmCU7fyCT32pjAOBgNVHQ8BAf8EBAMCBeAwJwYDVR0lBCAwHgYIKwYBBQUH +AwIGCCsGAQUFBwMEBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAi1pDjqy1 +bN9cLknPyblWdgaO0xSXJAvBpEaFnz88wmEPOmsg1889x7jJKhrGjTpjDJMeq3kh +ziDVCpOJesJOlUsa8ejOhMbcdiqHOWSk12bQC7dBbnAAXwO1Tr583IdLhC+ej64r +J4dBk7/wLBx2Deh8wxW+6TrIFNCyVptcHw76K2AkO11fscqS0sL/WVxqi1mjA9rV +KNGDIEIzqu13jZ3t0Cxc5AZ6dGHBALGNkfhjJ9SCpuPf8CmWNYHGWeIV0N4AB2SQ +gAjRYUKY4+zU3e+lUuudgTYZIM+zark6hLUAfXTeNRk6kGHod7x/Q9NvLB4SLwlI +DAzXJ9QHZyO1vQ== +-----END CERTIFICATE----- diff --git a/builtin/providers/consul/test-fixtures/userkey.pem b/builtin/providers/consul/test-fixtures/userkey.pem new file mode 100644 index 000000000..e3171bc28 --- /dev/null +++ b/builtin/providers/consul/test-fixtures/userkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA2t/OyP6Agzem+4j6F3HZbmyFOHZhd3hDtwHIsPNc9GAjRbUE +U4jE4B0fL/OrEIpA/GKJvE+oMLHM+dzuZJyjx7TO+KEDKy2GJSZZw10XWkiHrrfV +JT2sT4yKzpdxbZt66H8hWWhcHAjh2ZK5fdStvQMDDdFvmQInNdRMEb+DwquJHorI +0VO7RZv/U1yusKc0C15CakO64y3Hvmy1+gxSeWvFl+wJlcdWl7waZWXCqvwzDs0q +bJ4qCXSsfUx5/r5x70E+dUHF0kwRhaX5RB9NJX6lag21BNnERuFswWFiLjXQQnmz +Q3DqXJSdetDb9iCWKQi+fJ9aP8LAtNnBpeWACQIDAQABAoIBAQCRXS8zIoQrodyP +FkwzIfPseLqJ42WcOQ2QD+lATIEh9G+4rh5vdFh9GBpMeKLWW1wJw1AC90yW+p9O +G0NhIv9LdXQ4gIdgN93t8miPbdZCqgUjLwiqsSkttAPEbaRxzV915mk5vivemq+V +FvOG9Kdm7wcqODzL/DgaciMLboyNzrChltgybGKQIHAd9UFm+jE86IeeBsVcHuoL +0rEsYFKKzgdmIizjDOWPDSzVKL+dkiZ/8rYgoe1VtGV1DRWAWU5fawDFrdOxsGCh +Ob+rEmosTvONEQhB6GsdOQZ8++N6UTiJw32jqgieeP+Xj+K4XNG3nhP000DUIx/o +pRnj+KDhAoGBAPWXFbGHIMwEJCUV6OUXiY9Enb3A/Jf65f7cKbvo1i/nIbhzEv3v +LBtcUrsTmgTgNvuh3tF1RnwAUeYgedjdnALGJL16h3E0IWLAStaovoKgQyHHrtp9 +CEnOIj3PcPTFJVe+2FeV0+/kLjTHsZj9gMljzfxgswNdYfeGjXp4o1lVAoGBAOQm +06TW3smWW0FIRyyDNBwhQjD8tg3Yn0QJ+zFP6WMk5qbu/LeJnJSevBpQt1PvJ6xQ +kCj6Bi90jtLeuCW/8XLQjP46jQLU+3a74m3Nszgu9JVofiK6EPIsx62TGlwtIJfJ +U4+C5D/Piw/3qy6MjDbA1NJlSE4i2hAgGA79cDvlAoGBAN2o2sSbkOdyyWjLiKPV +BaxQowrUN2e45YONFQHsGf2sYEwJWNfm2elr/6OoAnhqIlYleGWWsuJSq5jIMRGi +myAJ1LlL8Rkkkwl9Q07RiPl/SngfsVq0RRnQOimNpIbXtWen8b3DlkFLssSihFHw +ZB/gu9cRNCFSVIzDXchvQAftAoGBAL0EzeOLgRhSUVhMoWrnaIzFoSkktU/TYF/m +RQ4dvqY9NDqpVQZaJDedKwpCRSBsytmgBU9tlSJL1ugtTTM5Srhsv+MAb0MhYRSF +pJqECS9K96ew4o+yx8dcAjJz5Sro2E/opCoJr0COmg+oiVIPbzsNl0SYVMcnaLJj +ZItGvW1hAoGBALeVUiqLgEDNQAIvprRlpJhU/ANpKm01ja9v66cgvmg62P9fcqb+ +yYPuJ2CwFDlb70KIDys6q9sTKUFykPDiKQgAFvEBQgAyOb3kdl4SXoUPbVDhMqwB +OfPznsXM6Y5LFNLzEi4n0QP4KsLc+wM52On5vnj7Mgvt5h2QlllPPTXy +-----END RSA PRIVATE KEY----- diff --git a/vendor/github.com/hashicorp/consul/api/agent.go b/vendor/github.com/hashicorp/consul/api/agent.go index e4466a651..3df013cc5 100644 --- a/vendor/github.com/hashicorp/consul/api/agent.go +++ b/vendor/github.com/hashicorp/consul/api/agent.go @@ -18,11 +18,12 @@ type AgentCheck struct { // AgentService represents a service known to the agent type AgentService struct { - ID string - Service string - Tags []string - Port int - Address string + ID string + Service string + Tags []string + Port int + Address string + EnableTagOverride bool } // AgentMember represents a cluster member known to the agent @@ -42,13 +43,14 @@ type AgentMember struct { // AgentServiceRegistration is used to register a new service type AgentServiceRegistration struct { - ID string `json:",omitempty"` - Name string `json:",omitempty"` - Tags []string `json:",omitempty"` - Port int `json:",omitempty"` - Address string `json:",omitempty"` - Check *AgentServiceCheck - Checks AgentServiceChecks + ID string `json:",omitempty"` + Name string `json:",omitempty"` + Tags []string `json:",omitempty"` + Port int `json:",omitempty"` + Address string `json:",omitempty"` + EnableTagOverride bool `json:",omitempty"` + Check *AgentServiceCheck + Checks AgentServiceChecks } // AgentCheckRegistration is used to register a new check @@ -196,23 +198,43 @@ func (a *Agent) ServiceDeregister(serviceID string) error { return nil } -// PassTTL is used to set a TTL check to the passing state +// PassTTL is used to set a TTL check to the passing state. +// +// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL(). +// The client interface will be removed in 0.8 or changed to use +// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9. func (a *Agent) PassTTL(checkID, note string) error { - return a.UpdateTTL(checkID, note, "pass") + return a.updateTTL(checkID, note, "pass") } -// WarnTTL is used to set a TTL check to the warning state +// WarnTTL is used to set a TTL check to the warning state. +// +// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL(). +// The client interface will be removed in 0.8 or changed to use +// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9. func (a *Agent) WarnTTL(checkID, note string) error { - return a.UpdateTTL(checkID, note, "warn") + return a.updateTTL(checkID, note, "warn") } -// FailTTL is used to set a TTL check to the failing state +// FailTTL is used to set a TTL check to the failing state. +// +// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL(). +// The client interface will be removed in 0.8 or changed to use +// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9. func (a *Agent) FailTTL(checkID, note string) error { - return a.UpdateTTL(checkID, note, "fail") + return a.updateTTL(checkID, note, "fail") } -// UpdateTTL is used to update the TTL of a check -func (a *Agent) UpdateTTL(checkID, note, status string) error { +// updateTTL is used to update the TTL of a check. This is the internal +// method that uses the old API that's present in Consul versions prior to +// 0.6.4. Since Consul didn't have an analogous "update" API before it seemed +// ok to break this (former) UpdateTTL in favor of the new UpdateTTL below, +// but keep the old Pass/Warn/Fail methods using the old API under the hood. +// +// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL(). +// The client interface will be removed in 0.8 and the server endpoints will +// be removed in 0.9. +func (a *Agent) updateTTL(checkID, note, status string) error { switch status { case "pass": case "warn": @@ -231,6 +253,51 @@ func (a *Agent) UpdateTTL(checkID, note, status string) error { return nil } +// checkUpdate is the payload for a PUT for a check update. +type checkUpdate struct { + // Status is one of the api.Health* states: HealthPassing + // ("passing"), HealthWarning ("warning"), or HealthCritical + // ("critical"). + Status string + + // Output is the information to post to the UI for operators as the + // output of the process that decided to hit the TTL check. This is + // different from the note field that's associated with the check + // itself. + Output string +} + +// UpdateTTL is used to update the TTL of a check. This uses the newer API +// that was introduced in Consul 0.6.4 and later. We translate the old status +// strings for compatibility (though a newer version of Consul will still be +// required to use this API). +func (a *Agent) UpdateTTL(checkID, output, status string) error { + switch status { + case "pass", HealthPassing: + status = HealthPassing + case "warn", HealthWarning: + status = HealthWarning + case "fail", HealthCritical: + status = HealthCritical + default: + return fmt.Errorf("Invalid status: %s", status) + } + + endpoint := fmt.Sprintf("/v1/agent/check/update/%s", checkID) + r := a.c.newRequest("PUT", endpoint) + r.obj = &checkUpdate{ + Status: status, + Output: output, + } + + _, resp, err := requireOK(a.c.doRequest(r)) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + // CheckRegister is used to register a new check with // the local agent func (a *Agent) CheckRegister(check *AgentCheckRegistration) error { diff --git a/vendor/github.com/hashicorp/consul/api/api.go b/vendor/github.com/hashicorp/consul/api/api.go index 0a2a76e5d..590b858e1 100644 --- a/vendor/github.com/hashicorp/consul/api/api.go +++ b/vendor/github.com/hashicorp/consul/api/api.go @@ -3,9 +3,11 @@ package api import ( "bytes" "crypto/tls" + "crypto/x509" "encoding/json" "fmt" "io" + "io/ioutil" "log" "net" "net/http" @@ -122,12 +124,58 @@ type Config struct { Token string } -// DefaultConfig returns a default configuration for the client +// TLSConfig is used to generate a TLSClientConfig that's useful for talking to +// Consul using TLS. +type TLSConfig struct { + // Address is the optional address of the Consul server. The port, if any + // will be removed from here and this will be set to the ServerName of the + // resulting config. + Address string + + // CAFile is the optional path to the CA certificate used for Consul + // communication, defaults to the system bundle if not specified. + CAFile string + + // CertFile is the optional path to the certificate for Consul + // communication. If this is set then you need to also set KeyFile. + CertFile string + + // KeyFile is the optional path to the private key for Consul communication. + // If this is set then you need to also set CertFile. + KeyFile string + + // InsecureSkipVerify if set to true will disable TLS host verification. + InsecureSkipVerify bool +} + +// DefaultConfig returns a default configuration for the client. By default this +// will pool and reuse idle connections to Consul. If you have a long-lived +// client object, this is the desired behavior and should make the most efficient +// use of the connections to Consul. If you don't reuse a client object , which +// is not recommended, then you may notice idle connections building up over +// time. To avoid this, use the DefaultNonPooledConfig() instead. func DefaultConfig() *Config { + return defaultConfig(cleanhttp.DefaultPooledTransport) +} + +// DefaultNonPooledConfig returns a default configuration for the client which +// does not pool connections. This isn't a recommended configuration because it +// will reconnect to Consul on every request, but this is useful to avoid the +// accumulation of idle connections if you make many client objects during the +// lifetime of your application. +func DefaultNonPooledConfig() *Config { + return defaultConfig(cleanhttp.DefaultTransport) +} + +// defaultConfig returns the default configuration for the client, using the +// given function to make the transport. +func defaultConfig(transportFn func() *http.Transport) *Config { config := &Config{ - Address: "127.0.0.1:8500", - Scheme: "http", - HttpClient: cleanhttp.DefaultClient(), + Address: "127.0.0.1:8500", + Scheme: "http", + HttpClient: &http.Client{ + Transport: transportFn(), + }, } if addr := os.Getenv("CONSUL_HTTP_ADDR"); addr != "" { @@ -172,10 +220,19 @@ func DefaultConfig() *Config { } if !doVerify { - transport := cleanhttp.DefaultTransport() - transport.TLSClientConfig = &tls.Config{ + tlsClientConfig, err := SetupTLSConfig(&TLSConfig{ InsecureSkipVerify: true, + }) + + // We don't expect this to fail given that we aren't + // parsing any of the input, but we panic just in case + // since this doesn't have an error return. + if err != nil { + panic(err) } + + transport := transportFn() + transport.TLSClientConfig = tlsClientConfig config.HttpClient.Transport = transport } } @@ -183,6 +240,50 @@ func DefaultConfig() *Config { return config } +// TLSConfig is used to generate a TLSClientConfig that's useful for talking to +// Consul using TLS. +func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) { + tlsClientConfig := &tls.Config{ + InsecureSkipVerify: tlsConfig.InsecureSkipVerify, + } + + if tlsConfig.Address != "" { + server := tlsConfig.Address + hasPort := strings.LastIndex(server, ":") > strings.LastIndex(server, "]") + if hasPort { + var err error + server, _, err = net.SplitHostPort(server) + if err != nil { + return nil, err + } + } + tlsClientConfig.ServerName = server + } + + if tlsConfig.CertFile != "" && tlsConfig.KeyFile != "" { + tlsCert, err := tls.LoadX509KeyPair(tlsConfig.CertFile, tlsConfig.KeyFile) + if err != nil { + return nil, err + } + tlsClientConfig.Certificates = []tls.Certificate{tlsCert} + } + + if tlsConfig.CAFile != "" { + data, err := ioutil.ReadFile(tlsConfig.CAFile) + if err != nil { + return nil, fmt.Errorf("failed to read CA file: %v", err) + } + + caPool := x509.NewCertPool() + if !caPool.AppendCertsFromPEM(data) { + return nil, fmt.Errorf("failed to parse CA certificate") + } + tlsClientConfig.RootCAs = caPool + } + + return tlsClientConfig, nil +} + // Client provides a client to the Consul API type Client struct { config Config diff --git a/vendor/github.com/hashicorp/consul/api/catalog.go b/vendor/github.com/hashicorp/consul/api/catalog.go index cf64bd909..52a00b304 100644 --- a/vendor/github.com/hashicorp/consul/api/catalog.go +++ b/vendor/github.com/hashicorp/consul/api/catalog.go @@ -6,13 +6,14 @@ type Node struct { } type CatalogService struct { - Node string - Address string - ServiceID string - ServiceName string - ServiceAddress string - ServiceTags []string - ServicePort int + Node string + Address string + ServiceID string + ServiceName string + ServiceAddress string + ServiceTags []string + ServicePort int + ServiceEnableTagOverride bool } type CatalogNode struct { diff --git a/vendor/github.com/hashicorp/consul/api/health.go b/vendor/github.com/hashicorp/consul/api/health.go index 1a273e087..5bb403f55 100644 --- a/vendor/github.com/hashicorp/consul/api/health.go +++ b/vendor/github.com/hashicorp/consul/api/health.go @@ -4,6 +4,16 @@ import ( "fmt" ) +const ( + // HealthAny is special, and is used as a wild card, + // not as a specific state. + HealthAny = "any" + HealthUnknown = "unknown" + HealthPassing = "passing" + HealthWarning = "warning" + HealthCritical = "critical" +) + // HealthCheck is used to represent a single check type HealthCheck struct { Node string @@ -85,7 +95,7 @@ func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) r.params.Set("tag", tag) } if passingOnly { - r.params.Set("passing", "1") + r.params.Set(HealthPassing, "1") } rtt, resp, err := requireOK(h.c.doRequest(r)) if err != nil { @@ -108,11 +118,11 @@ func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) // The wildcard "any" state can also be used for all checks. func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) { switch state { - case "any": - case "warning": - case "critical": - case "passing": - case "unknown": + case HealthAny: + case HealthWarning: + case HealthCritical: + case HealthPassing: + case HealthUnknown: default: return nil, nil, fmt.Errorf("Unsupported state: %v", state) } diff --git a/vendor/github.com/hashicorp/consul/api/kv.go b/vendor/github.com/hashicorp/consul/api/kv.go index 688b3a09d..3dac2583c 100644 --- a/vendor/github.com/hashicorp/consul/api/kv.go +++ b/vendor/github.com/hashicorp/consul/api/kv.go @@ -23,6 +23,43 @@ type KVPair struct { // KVPairs is a list of KVPair objects type KVPairs []*KVPair +// KVOp constants give possible operations available in a KVTxn. +type KVOp string + +const ( + KVSet KVOp = "set" + KVDelete = "delete" + KVDeleteCAS = "delete-cas" + KVDeleteTree = "delete-tree" + KVCAS = "cas" + KVLock = "lock" + KVUnlock = "unlock" + KVGet = "get" + KVGetTree = "get-tree" + KVCheckSession = "check-session" + KVCheckIndex = "check-index" +) + +// KVTxnOp defines a single operation inside a transaction. +type KVTxnOp struct { + Verb string + Key string + Value []byte + Flags uint64 + Index uint64 + Session string +} + +// KVTxnOps defines a set of operations to be performed inside a single +// transaction. +type KVTxnOps []*KVTxnOp + +// KVTxnResponse has the outcome of a transaction. +type KVTxnResponse struct { + Results []*KVPair + Errors TxnErrors +} + // KV is used to manipulate the K/V API type KV struct { c *Client @@ -238,3 +275,122 @@ func (k *KV) deleteInternal(key string, params map[string]string, q *WriteOption res := strings.Contains(string(buf.Bytes()), "true") return res, qm, nil } + +// TxnOp is the internal format we send to Consul. It's not specific to KV, +// though currently only KV operations are supported. +type TxnOp struct { + KV *KVTxnOp +} + +// TxnOps is a list of transaction operations. +type TxnOps []*TxnOp + +// TxnResult is the internal format we receive from Consul. +type TxnResult struct { + KV *KVPair +} + +// TxnResults is a list of TxnResult objects. +type TxnResults []*TxnResult + +// TxnError is used to return information about an operation in a transaction. +type TxnError struct { + OpIndex int + What string +} + +// TxnErrors is a list of TxnError objects. +type TxnErrors []*TxnError + +// TxnResponse is the internal format we receive from Consul. +type TxnResponse struct { + Results TxnResults + Errors TxnErrors +} + +// Txn is used to apply multiple KV operations in a single, atomic transaction. +// +// Note that Go will perform the required base64 encoding on the values +// automatically because the type is a byte slice. Transactions are defined as a +// list of operations to perform, using the KVOp constants and KVTxnOp structure +// to define operations. If any operation fails, none of the changes are applied +// to the state store. Note that this hides the internal raw transaction interface +// and munges the input and output types into KV-specific ones for ease of use. +// If there are more non-KV operations in the future we may break out a new +// transaction API client, but it will be easy to keep this KV-specific variant +// supported. +// +// Even though this is generally a write operation, we take a QueryOptions input +// and return a QueryMeta output. If the transaction contains only read ops, then +// Consul will fast-path it to a different endpoint internally which supports +// consistency controls, but not blocking. If there are write operations then +// the request will always be routed through raft and any consistency settings +// will be ignored. +// +// Here's an example: +// +// ops := KVTxnOps{ +// &KVTxnOp{ +// Verb: KVLock, +// Key: "test/lock", +// Session: "adf4238a-882b-9ddc-4a9d-5b6758e4159e", +// Value: []byte("hello"), +// }, +// &KVTxnOp{ +// Verb: KVGet, +// Key: "another/key", +// }, +// } +// ok, response, _, err := kv.Txn(&ops, nil) +// +// If there is a problem making the transaction request then an error will be +// returned. Otherwise, the ok value will be true if the transaction succeeded +// or false if it was rolled back. The response is a structured return value which +// will have the outcome of the transaction. Its Results member will have entries +// for each operation. Deleted keys will have a nil entry in the, and to save +// space, the Value of each key in the Results will be nil unless the operation +// is a KVGet. If the transaction was rolled back, the Errors member will have +// entries referencing the index of the operation that failed along with an error +// message. +func (k *KV) Txn(txn KVTxnOps, q *QueryOptions) (bool, *KVTxnResponse, *QueryMeta, error) { + r := k.c.newRequest("PUT", "/v1/txn") + r.setQueryOptions(q) + + // Convert into the internal format since this is an all-KV txn. + ops := make(TxnOps, 0, len(txn)) + for _, kvOp := range txn { + ops = append(ops, &TxnOp{KV: kvOp}) + } + r.obj = ops + rtt, resp, err := k.c.doRequest(r) + if err != nil { + return false, nil, nil, err + } + defer resp.Body.Close() + + qm := &QueryMeta{} + parseQueryMeta(resp, qm) + qm.RequestTime = rtt + + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusConflict { + var txnResp TxnResponse + if err := decodeBody(resp, &txnResp); err != nil { + return false, nil, nil, err + } + + // Convert from the internal format. + kvResp := KVTxnResponse{ + Errors: txnResp.Errors, + } + for _, result := range txnResp.Results { + kvResp.Results = append(kvResp.Results, result.KV) + } + return resp.StatusCode == http.StatusOK, &kvResp, qm, nil + } + + var buf bytes.Buffer + if _, err := io.Copy(&buf, resp.Body); err != nil { + return false, nil, nil, fmt.Errorf("Failed to read response: %v", err) + } + return false, nil, nil, fmt.Errorf("Failed request: %s", buf.String()) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index b5498dbdc..9b21d0098 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -890,9 +890,11 @@ "revisionTime": "2016-07-26T16:33:11Z" }, { + "checksumSHA1": "glOabn8rkJvz7tjz/xfX4lmt070=", "comment": "v0.6.3-28-g3215b87", "path": "github.com/hashicorp/consul/api", - "revision": "3215b8727f44c778dd7045dcfd5ac42735c581a9" + "revision": "d4a8a43d2b600e662a50a75be70daed5fad8dd2d", + "revisionTime": "2016-06-04T06:35:46Z" }, { "path": "github.com/hashicorp/errwrap", diff --git a/website/source/docs/providers/consul/index.html.markdown b/website/source/docs/providers/consul/index.html.markdown index 793b1fc30..1d39f95a3 100644 --- a/website/source/docs/providers/consul/index.html.markdown +++ b/website/source/docs/providers/consul/index.html.markdown @@ -46,4 +46,8 @@ The following arguments are supported: * `address` - (Optional) The HTTP(S) API address of the agent to use. Defaults to "127.0.0.1:8500". * `scheme` - (Optional) The URL scheme of the agent to use ("http" or "https"). Defaults to "http". * `datacenter` - (Optional) The datacenter to use. Defaults to that of the agent. +* `token` - (Optional) The ACL token to use by default when making requests to the agent. +* `ca_file` - (Optional) A path to a PEM-encoded certificate authority used to verify the remote agent's certificate. +* `cert_file` - (Optional) A path to a PEM-encoded certificate provided to the remote agent; requires use of `key_file`. +* `key_file`- (Optional) A path to a PEM-encoded private key, required if `cert_file` is specified.