diff --git a/command/agent/agent.go b/command/agent/agent.go index f43bcac69..827ccd2c4 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -376,6 +376,14 @@ func (a *Agent) consulConfig() (*consul.Config, error) { if a.config.ReconnectTimeoutWan != 0 { base.SerfWANConfig.ReconnectTimeout = a.config.ReconnectTimeoutWan } + if a.config.EncryptVerifyIncoming != nil { + base.SerfWANConfig.MemberlistConfig.GossipVerifyIncoming = *a.config.EncryptVerifyIncoming + base.SerfLANConfig.MemberlistConfig.GossipVerifyIncoming = *a.config.EncryptVerifyIncoming + } + if a.config.EncryptVerifyOutgoing != nil { + base.SerfWANConfig.MemberlistConfig.GossipVerifyOutgoing = *a.config.EncryptVerifyOutgoing + base.SerfLANConfig.MemberlistConfig.GossipVerifyOutgoing = *a.config.EncryptVerifyOutgoing + } if a.config.AdvertiseAddrs.RPC != nil { base.RPCAdvertise = a.config.AdvertiseAddrs.RPC } diff --git a/command/agent/config.go b/command/agent/config.go index 16069919d..05fa770e7 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -365,6 +365,12 @@ type Config struct { // Encryption key to use for the Serf communication EncryptKey string `mapstructure:"encrypt" json:"-"` + // EncryptVerifyIncoming and EncryptVerifyOutgoing are used to enforce + // incoming/outgoing gossip encryption and can be used to upshift to + // encrypted gossip on a running cluster. + EncryptVerifyIncoming *bool `mapstructure:"encrypt_verify_incoming"` + EncryptVerifyOutgoing *bool `mapstructure:"encrypt_verify_outgoing"` + // LogLevel is the level of the logs to putout LogLevel string `mapstructure:"log_level"` @@ -864,6 +870,9 @@ func DefaultConfig() *Config { RetryIntervalWan: 30 * time.Second, TLSMinVersion: "tls10", + + EncryptVerifyIncoming: Bool(true), + EncryptVerifyOutgoing: Bool(true), } } @@ -1477,6 +1486,12 @@ func MergeConfig(a, b *Config) *Config { if b.EncryptKey != "" { result.EncryptKey = b.EncryptKey } + if b.EncryptVerifyIncoming != nil { + result.EncryptVerifyIncoming = b.EncryptVerifyIncoming + } + if b.EncryptVerifyOutgoing != nil { + result.EncryptVerifyOutgoing = b.EncryptVerifyOutgoing + } if b.LogLevel != "" { result.LogLevel = b.LogLevel } diff --git a/command/agent/config_test.go b/command/agent/config_test.go index d0413524b..2d64264f0 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -123,6 +123,18 @@ func TestDecodeConfig(t *testing.T) { t.Fatalf("bad: %#v", config) } + input = `{"encrypt_verify_incoming":true, "encrypt_verify_outgoing":true}` + config, err = DecodeConfig(bytes.NewReader([]byte(input))) + if err != nil { + t.Fatalf("err: %s", err) + } + if config.EncryptVerifyIncoming == nil || !*config.EncryptVerifyIncoming { + t.Fatalf("bad: %#v", config) + } + if config.EncryptVerifyOutgoing == nil || !*config.EncryptVerifyOutgoing { + t.Fatalf("bad: %#v", config) + } + // DNS setup input = `{"ports": {"dns": 8500}, "recursors": ["8.8.8.8","8.8.4.4"], "recursor":"127.0.0.1", "domain": "foobar"}` config, err = DecodeConfig(bytes.NewReader([]byte(input))) diff --git a/vendor/github.com/hashicorp/memberlist/config.go b/vendor/github.com/hashicorp/memberlist/config.go index 2f43d14cb..5cad4ed54 100644 --- a/vendor/github.com/hashicorp/memberlist/config.go +++ b/vendor/github.com/hashicorp/memberlist/config.go @@ -141,6 +141,16 @@ type Config struct { GossipNodes int GossipToTheDeadTime time.Duration + // GossipVerifyIncoming controls whether to enforce encryption for incoming + // gossip. It is used for upshifting from unencrypted to encrypted gossip on + // a running cluster. + GossipVerifyIncoming bool + + // GossipVerifyOutgoing controls whether to enforce encryption for outgoing + // gossip. It is used for upshifting from unencrypted to encrypted gossip on + // a running cluster. + GossipVerifyOutgoing bool + // EnableCompression is used to control message compression. This can // be used to reduce bandwidth usage at the cost of slightly more CPU // utilization. This is only available starting at protocol version 1. @@ -233,9 +243,11 @@ func DefaultLANConfig() *Config { DisableTcpPings: false, // TCP pings are safe, even with mixed versions AwarenessMaxMultiplier: 8, // Probe interval backs off to 8 seconds - GossipNodes: 3, // Gossip to 3 nodes - GossipInterval: 200 * time.Millisecond, // Gossip more rapidly - GossipToTheDeadTime: 30 * time.Second, // Same as push/pull + GossipNodes: 3, // Gossip to 3 nodes + GossipInterval: 200 * time.Millisecond, // Gossip more rapidly + GossipToTheDeadTime: 30 * time.Second, // Same as push/pull + GossipVerifyIncoming: true, + GossipVerifyOutgoing: true, EnableCompression: true, // Enable compression by default diff --git a/vendor/github.com/hashicorp/memberlist/memberlist.go b/vendor/github.com/hashicorp/memberlist/memberlist.go index 2aba22322..e4b0d7347 100644 --- a/vendor/github.com/hashicorp/memberlist/memberlist.go +++ b/vendor/github.com/hashicorp/memberlist/memberlist.go @@ -334,7 +334,7 @@ func (m *Memberlist) setAlive() error { addr, port, err := m.transport.FinalAdvertiseAddr( m.config.AdvertiseAddr, m.config.AdvertisePort) if err != nil { - return fmt.Errorf("Failed to get final advertise address: %v") + return fmt.Errorf("Failed to get final advertise address: %v", err) } // Check if this is a public address without encryption diff --git a/vendor/github.com/hashicorp/memberlist/net.go b/vendor/github.com/hashicorp/memberlist/net.go index e0036d01d..65a60159d 100644 --- a/vendor/github.com/hashicorp/memberlist/net.go +++ b/vendor/github.com/hashicorp/memberlist/net.go @@ -283,8 +283,13 @@ func (m *Memberlist) ingestPacket(buf []byte, from net.Addr, timestamp time.Time // Decrypt the payload plain, err := decryptPayload(m.config.Keyring.GetKeys(), buf, nil) if err != nil { - m.logger.Printf("[ERR] memberlist: Decrypt packet failed: %v %s", err, LogAddress(from)) - return + if !m.config.GossipVerifyIncoming { + // Treat the message as plaintext + plain = buf + } else { + m.logger.Printf("[ERR] memberlist: Decrypt packet failed: %v %s", err, LogAddress(from)) + return + } } // Continue processing the plaintext buffer @@ -557,7 +562,7 @@ func (m *Memberlist) encodeAndSendMsg(addr string, msgType messageType, msg inte func (m *Memberlist) sendMsg(addr string, msg []byte) error { // Check if we can piggy back any messages bytesAvail := m.config.UDPBufferSize - len(msg) - compoundHeaderOverhead - if m.config.EncryptionEnabled() { + if m.config.EncryptionEnabled() && m.config.GossipVerifyOutgoing { bytesAvail -= encryptOverhead(m.encryptionVersion()) } extra := m.getBroadcasts(compoundOverhead, bytesAvail) @@ -621,7 +626,7 @@ func (m *Memberlist) rawSendMsgPacket(addr string, node *Node, msg []byte) error } // Check if we have encryption enabled - if m.config.EncryptionEnabled() { + if m.config.EncryptionEnabled() && m.config.GossipVerifyOutgoing { // Encrypt the payload var buf bytes.Buffer primaryKey := m.config.Keyring.GetPrimaryKey() @@ -652,7 +657,7 @@ func (m *Memberlist) rawSendMsgStream(conn net.Conn, sendBuf []byte) error { } // Check if encryption is enabled - if m.config.EncryptionEnabled() { + if m.config.EncryptionEnabled() && m.config.GossipVerifyOutgoing { crypt, err := m.encryptLocalState(sendBuf) if err != nil { m.logger.Printf("[ERROR] memberlist: Failed to encrypt local state: %v", err) @@ -876,7 +881,7 @@ func (m *Memberlist) readStream(conn net.Conn) (messageType, io.Reader, *codec.D // Reset message type and bufConn msgType = messageType(plain[0]) bufConn = bytes.NewReader(plain[1:]) - } else if m.config.EncryptionEnabled() { + } else if m.config.EncryptionEnabled() && m.config.GossipVerifyIncoming { return 0, nil, nil, fmt.Errorf("Encryption is configured but remote state is not encrypted") } diff --git a/vendor/github.com/hashicorp/memberlist/state.go b/vendor/github.com/hashicorp/memberlist/state.go index 71bf6f34d..8513361b1 100644 --- a/vendor/github.com/hashicorp/memberlist/state.go +++ b/vendor/github.com/hashicorp/memberlist/state.go @@ -40,6 +40,11 @@ func (n *Node) Address() string { return joinHostPort(n.Addr.String(), n.Port) } +// String returns the node name +func (n *Node) String() string { + return n.Name +} + // NodeState is used to manage our state view of another node type nodeState struct { Node diff --git a/vendor/github.com/hashicorp/memberlist/tag.sh b/vendor/github.com/hashicorp/memberlist/tag.sh new file mode 100755 index 000000000..cd16623a7 --- /dev/null +++ b/vendor/github.com/hashicorp/memberlist/tag.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -e + +# The version must be supplied from the environment. Do not include the +# leading "v". +if [ -z $VERSION ]; then + echo "Please specify a version." + exit 1 +fi + +# Generate the tag. +echo "==> Tagging version $VERSION..." +git commit --allow-empty -a --gpg-sign=348FFC4C -m "Release v$VERSION" +git tag -a -m "Version $VERSION" -s -u 348FFC4C "v${VERSION}" master + +exit 0 diff --git a/vendor/vendor.json b/vendor/vendor.json index 770afdf7a..8f2c0ce3a 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -642,10 +642,10 @@ "revisionTime": "2015-06-09T07:04:31Z" }, { - "checksumSHA1": "JJsKjmgNTUTaEHEEAQgb9jCGGiM=", + "checksumSHA1": "AoIvQFHycqypYK57ZjiWzlQmdwk=", "path": "github.com/hashicorp/memberlist", - "revision": "6cc6075ba9fba1915fa0416f00d2b4efa9dc2262", - "revisionTime": "2017-03-17T22:24:04Z" + "revision": "16fe34d996eba2b68f6f46f26c51c617c6bc1bf0", + "revisionTime": "2017-05-26T19:17:51Z" }, { "checksumSHA1": "qnlqWJYV81ENr61SZk9c65R1mDo=", diff --git a/website/source/docs/agent/encryption.html.md b/website/source/docs/agent/encryption.html.md index 6f182ee32..83e2db0a0 100644 --- a/website/source/docs/agent/encryption.html.md +++ b/website/source/docs/agent/encryption.html.md @@ -52,6 +52,24 @@ $ consul agent -data-dir=/tmp/consul -config-file=encrypt.json All nodes within a Consul cluster must share the same encryption key in order to send and receive cluster information. +## Configuring Gossip Encryption on an existing cluster + +As of version 0.8.4, Consul supports upshifting to encrypted gossip on a running cluster +through the following process. + +1. Generate an encryption key using [`consul keygen`](/docs/commands/keygen.html) +2. Set the [`encrypt`](/docs/agent/options.html#_encrypt) key in the agent configuration and set +[`encrypt_verify_incoming`](/docs/agent/options.html#encrypt_verify_incoming) and +[`encrypt_verify_outgoing`](/docs/agent/options.html#encrypt_verify_outgoing) to `false`, doing a +rolling update of the cluster with these new values. After this step, the agents will be able to +decrypt gossip but will not yet be sending encrypted traffic. +3. Remove the [`encrypt_verify_outgoing`](/docs/agent/options.html#encrypt_verify_outgoing) setting +to change it back to false (the default) and perform another rolling update of the cluster. The +agents will now be sending encrypted gossip but will still allow incoming unencrypted traffic. +4. Remove the [`encrypt_verify_incoming`](/docs/agent/options.html#encrypt_verify_incoming) setting +to change it back to false (the default) and perform a final rolling update of the cluster. All the +agents will now be strictly enforcing encrypted gossip. + ## RPC Encryption with TLS Consul supports using TLS to verify the authenticity of servers and clients. To enable this, diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md index 2961c2bd5..48416f9d8 100644 --- a/website/source/docs/agent/options.html.md +++ b/website/source/docs/agent/options.html.md @@ -708,6 +708,18 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass * `encrypt` Equivalent to the [`-encrypt` command-line flag](#_encrypt). +* `encrypt_verify_incoming` - + This is an optional parameter that can be used to disable enforcing encryption for incoming gossip in order + to upshift from unencrypted to encrypted gossip on a running cluster. See [this section] + (/docs/agent/encryption.html#configuring-gossip-encryption-on-an-existing-cluster) for more information. + Defaults to true. + +* `encrypt_verify_outgoing` - + This is an optional parameter that can be used to disable enforcing encryption for outgoing gossip in order + to upshift from unencrypted to encrypted gossip on a running cluster. See [this section] + (/docs/agent/encryption.html#configuring-gossip-encryption-on-an-existing-cluster) for more information. + Defaults to true. + * `key_file` This provides a the file path to a PEM-encoded private key. The key is used with the certificate to verify the agent's authenticity. This must be provided along with [`cert_file`](#cert_file).