From ff85bec45bdce3d296a229adb47e7d29415f8a05 Mon Sep 17 00:00:00 2001 From: Dustin Hooten Date: Mon, 9 Aug 2021 14:58:06 -0600 Subject: [PATCH] Secure cluster traffic via mutual TLS (#2237) * Add TLS option to gossip cluster Co-authored-by: Sharad Gaur Signed-off-by: Dustin Hooten * generate new certs that expire in 100 years Signed-off-by: Dustin Hooten * Fix tls_connection attributes Signed-off-by: Dustin Hooten * Improve error message Signed-off-by: Dustin Hooten * Fix tls client config docs Signed-off-by: Dustin Hooten * Add capacity arg to message buffer Signed-off-by: Dustin Hooten * fix formatting Signed-off-by: Dustin Hooten * Update version; add version validation Signed-off-by: Dustin Hooten * use lru cache for connection pool Signed-off-by: Dustin Hooten * lock reading from the connection Signed-off-by: Dustin Hooten * when extracting net.Conn from tlsConn, lock and throw away wrapper Signed-off-by: Dustin Hooten * Add mutex to connection pool to protect cache Signed-off-by: Dustin Hooten * fix linting Signed-off-by: Dustin Hooten Co-authored-by: Sharad Gaur --- .gitignore | 2 + cluster/cluster.go | 10 +- cluster/cluster_test.go | 72 ++++++ cluster/clusterpb/cluster.pb.go | 350 +++++++++++++++++++++++++- cluster/clusterpb/cluster.proto | 11 + cluster/connection_pool.go | 84 +++++++ cluster/testdata/certs/ca-config.json | 13 + cluster/testdata/certs/ca-csr.json | 16 ++ cluster/testdata/certs/ca-key.pem | 27 ++ cluster/testdata/certs/ca.csr | 17 ++ cluster/testdata/certs/ca.pem | 22 ++ cluster/testdata/certs/node1-csr.json | 16 ++ cluster/testdata/certs/node1-key.pem | 27 ++ cluster/testdata/certs/node1.csr | 18 ++ cluster/testdata/certs/node1.pem | 24 ++ cluster/testdata/certs/node2-csr.json | 16 ++ cluster/testdata/certs/node2-key.pem | 27 ++ cluster/testdata/certs/node2.csr | 18 ++ cluster/testdata/certs/node2.pem | 24 ++ cluster/testdata/tls_config_node1.yml | 9 + cluster/testdata/tls_config_node2.yml | 9 + cluster/tls_config.go | 45 ++++ cluster/tls_connection.go | 188 ++++++++++++++ cluster/tls_connection_test.go | 126 ++++++++++ cluster/tls_transport.go | 346 +++++++++++++++++++++++++ cluster/tls_transport_test.go | 213 ++++++++++++++++ cmd/alertmanager/main.go | 7 + docs/https.md | 65 ++++- examples/ha/tls/Makefile | 27 ++ examples/ha/tls/Procfile | 7 + examples/ha/tls/README.md | 18 ++ examples/ha/tls/certs/ca-config.json | 13 + examples/ha/tls/certs/ca-csr.json | 16 ++ examples/ha/tls/certs/ca-key.pem | 27 ++ examples/ha/tls/certs/ca.csr | 17 ++ examples/ha/tls/certs/ca.pem | 22 ++ examples/ha/tls/certs/node1-csr.json | 16 ++ examples/ha/tls/certs/node1-key.pem | 27 ++ examples/ha/tls/certs/node1.csr | 18 ++ examples/ha/tls/certs/node1.pem | 24 ++ examples/ha/tls/certs/node2-csr.json | 16 ++ examples/ha/tls/certs/node2-key.pem | 27 ++ examples/ha/tls/certs/node2.csr | 18 ++ examples/ha/tls/certs/node2.pem | 24 ++ examples/ha/tls/tls_config_node1.yml | 9 + examples/ha/tls/tls_config_node2.yml | 9 + go.mod | 1 + go.sum | 3 +- 48 files changed, 2125 insertions(+), 16 deletions(-) create mode 100644 cluster/connection_pool.go create mode 100644 cluster/testdata/certs/ca-config.json create mode 100644 cluster/testdata/certs/ca-csr.json create mode 100644 cluster/testdata/certs/ca-key.pem create mode 100644 cluster/testdata/certs/ca.csr create mode 100644 cluster/testdata/certs/ca.pem create mode 100644 cluster/testdata/certs/node1-csr.json create mode 100644 cluster/testdata/certs/node1-key.pem create mode 100644 cluster/testdata/certs/node1.csr create mode 100644 cluster/testdata/certs/node1.pem create mode 100644 cluster/testdata/certs/node2-csr.json create mode 100644 cluster/testdata/certs/node2-key.pem create mode 100644 cluster/testdata/certs/node2.csr create mode 100644 cluster/testdata/certs/node2.pem create mode 100644 cluster/testdata/tls_config_node1.yml create mode 100644 cluster/testdata/tls_config_node2.yml create mode 100644 cluster/tls_config.go create mode 100644 cluster/tls_connection.go create mode 100644 cluster/tls_connection_test.go create mode 100644 cluster/tls_transport.go create mode 100644 cluster/tls_transport_test.go create mode 100644 examples/ha/tls/Makefile create mode 100644 examples/ha/tls/Procfile create mode 100644 examples/ha/tls/README.md create mode 100644 examples/ha/tls/certs/ca-config.json create mode 100644 examples/ha/tls/certs/ca-csr.json create mode 100644 examples/ha/tls/certs/ca-key.pem create mode 100644 examples/ha/tls/certs/ca.csr create mode 100644 examples/ha/tls/certs/ca.pem create mode 100644 examples/ha/tls/certs/node1-csr.json create mode 100644 examples/ha/tls/certs/node1-key.pem create mode 100644 examples/ha/tls/certs/node1.csr create mode 100644 examples/ha/tls/certs/node1.pem create mode 100644 examples/ha/tls/certs/node2-csr.json create mode 100644 examples/ha/tls/certs/node2-key.pem create mode 100644 examples/ha/tls/certs/node2.csr create mode 100644 examples/ha/tls/certs/node2.pem create mode 100644 examples/ha/tls/tls_config_node1.yml create mode 100644 examples/ha/tls/tls_config_node2.yml diff --git a/.gitignore b/.gitignore index c8999f57..aa4b7800 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,9 @@ !.golangci.yml !/cli/testdata/*.yml !/cli/config/testdata/*.yml +!/cluster/testdata/*.yml !/config/testdata/*.yml +!/examples/ha/tls/*.yml !/notify/email/testdata/*.yml !/doc/examples/simple.yml !/circle.yml diff --git a/cluster/cluster.go b/cluster/cluster.go index 4c048fac..9214bc62 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -29,7 +29,6 @@ import ( "github.com/hashicorp/memberlist" "github.com/oklog/ulid" "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" ) @@ -141,6 +140,7 @@ func Create( tcpTimeout time.Duration, probeTimeout time.Duration, probeInterval time.Duration, + tlsTransportConfig *TLSTransportConfig, ) (*Peer, error) { bindHost, bindPortStr, err := net.SplitHostPort(bindAddr) if err != nil { @@ -235,6 +235,14 @@ func Create( p.setInitialFailed(resolvedPeers, bindAddr) } + if tlsTransportConfig != nil { + level.Info(l).Log("msg", "using TLS for gossip") + cfg.Transport, err = NewTLSTransport(context.Background(), l, reg, cfg.BindAddr, cfg.BindPort, tlsTransportConfig) + if err != nil { + return nil, errors.Wrap(err, "tls transport") + } + } + ml, err := memberlist.Create(cfg) if err != nil { return nil, errors.Wrap(err, "create memberlist") diff --git a/cluster/cluster_test.go b/cluster/cluster_test.go index f9413fac..cbbe1d10 100644 --- a/cluster/cluster_test.go +++ b/cluster/cluster_test.go @@ -35,6 +35,7 @@ func TestClusterJoinAndReconnect(t *testing.T) { t.Run("TestReconnect", testReconnect) t.Run("TestRemoveFailedPeers", testRemoveFailedPeers) t.Run("TestInitiallyFailingPeers", testInitiallyFailingPeers) + t.Run("TestTLSConnection", testTLSConnection) } func testJoinLeave(t *testing.T) { @@ -51,6 +52,7 @@ func testJoinLeave(t *testing.T) { DefaultTcpTimeout, DefaultProbeTimeout, DefaultProbeInterval, + nil, ) require.NoError(t, err) require.NotNil(t, p) @@ -83,6 +85,7 @@ func testJoinLeave(t *testing.T) { DefaultTcpTimeout, DefaultProbeTimeout, DefaultProbeInterval, + nil, ) require.NoError(t, err) require.NotNil(t, p2) @@ -116,6 +119,7 @@ func testReconnect(t *testing.T) { DefaultTcpTimeout, DefaultProbeTimeout, DefaultProbeInterval, + nil, ) require.NoError(t, err) require.NotNil(t, p) @@ -139,6 +143,7 @@ func testReconnect(t *testing.T) { DefaultTcpTimeout, DefaultProbeTimeout, DefaultProbeInterval, + nil, ) require.NoError(t, err) require.NotNil(t, p2) @@ -177,6 +182,7 @@ func testRemoveFailedPeers(t *testing.T) { DefaultTcpTimeout, DefaultProbeTimeout, DefaultProbeInterval, + nil, ) require.NoError(t, err) require.NotNil(t, p) @@ -226,6 +232,7 @@ func testInitiallyFailingPeers(t *testing.T) { DefaultTcpTimeout, DefaultProbeTimeout, DefaultProbeInterval, + nil, ) require.NoError(t, err) require.NotNil(t, p) @@ -254,3 +261,68 @@ func testInitiallyFailingPeers(t *testing.T) { require.Equal(t, expectedLen, len(p.failedPeers)) } } + +func testTLSConnection(t *testing.T) { + logger := log.NewNopLogger() + tlsTransportConfig1, err := GetTLSTransportConfig("./testdata/tls_config_node1.yml") + require.NoError(t, err) + p1, err := Create( + logger, + prometheus.NewRegistry(), + "127.0.0.1:0", + "", + []string{}, + true, + DefaultPushPullInterval, + DefaultGossipInterval, + DefaultTcpTimeout, + DefaultProbeTimeout, + DefaultProbeInterval, + tlsTransportConfig1, + ) + require.NoError(t, err) + require.NotNil(t, p1) + err = p1.Join( + DefaultReconnectInterval, + DefaultReconnectTimeout, + ) + require.NoError(t, err) + require.False(t, p1.Ready()) + require.Equal(t, p1.Status(), "settling") + go p1.Settle(context.Background(), 0*time.Second) + p1.WaitReady(context.Background()) + require.Equal(t, p1.Status(), "ready") + + // Create the peer who joins the first. + tlsTransportConfig2, err := GetTLSTransportConfig("./testdata/tls_config_node2.yml") + require.NoError(t, err) + p2, err := Create( + logger, + prometheus.NewRegistry(), + "127.0.0.1:0", + "", + []string{p1.Self().Address()}, + true, + DefaultPushPullInterval, + DefaultGossipInterval, + DefaultTcpTimeout, + DefaultProbeTimeout, + DefaultProbeInterval, + tlsTransportConfig2, + ) + require.NoError(t, err) + require.NotNil(t, p2) + err = p2.Join( + DefaultReconnectInterval, + DefaultReconnectTimeout, + ) + require.NoError(t, err) + go p2.Settle(context.Background(), 0*time.Second) + + require.Equal(t, 2, p1.ClusterSize()) + p2.Leave(0 * time.Second) + require.Equal(t, 1, p1.ClusterSize()) + require.Equal(t, 1, len(p1.failedPeers)) + require.Equal(t, p2.Self().Address(), p1.peers[p2.Self().Address()].Node.Address()) + require.Equal(t, p2.Name(), p1.failedPeers[0].Name) +} diff --git a/cluster/clusterpb/cluster.pb.go b/cluster/clusterpb/cluster.pb.go index 7f87e65b..9bb8e269 100644 --- a/cluster/clusterpb/cluster.pb.go +++ b/cluster/clusterpb/cluster.pb.go @@ -24,6 +24,31 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +type MemberlistMessage_Kind int32 + +const ( + MemberlistMessage_STREAM MemberlistMessage_Kind = 0 + MemberlistMessage_PACKET MemberlistMessage_Kind = 1 +) + +var MemberlistMessage_Kind_name = map[int32]string{ + 0: "STREAM", + 1: "PACKET", +} + +var MemberlistMessage_Kind_value = map[string]int32{ + "STREAM": 0, + "PACKET": 1, +} + +func (x MemberlistMessage_Kind) String() string { + return proto.EnumName(MemberlistMessage_Kind_name, int32(x)) +} + +func (MemberlistMessage_Kind) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_3cfb3b8ec240c376, []int{2, 0} +} + type Part struct { Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` @@ -105,26 +130,79 @@ func (m *FullState) XXX_DiscardUnknown() { var xxx_messageInfo_FullState proto.InternalMessageInfo +type MemberlistMessage struct { + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Kind MemberlistMessage_Kind `protobuf:"varint,2,opt,name=kind,proto3,enum=clusterpb.MemberlistMessage_Kind" json:"kind,omitempty"` + FromAddr string `protobuf:"bytes,3,opt,name=from_addr,json=fromAddr,proto3" json:"from_addr,omitempty"` + Msg []byte `protobuf:"bytes,4,opt,name=msg,proto3" json:"msg,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MemberlistMessage) Reset() { *m = MemberlistMessage{} } +func (m *MemberlistMessage) String() string { return proto.CompactTextString(m) } +func (*MemberlistMessage) ProtoMessage() {} +func (*MemberlistMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_3cfb3b8ec240c376, []int{2} +} +func (m *MemberlistMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MemberlistMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MemberlistMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MemberlistMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_MemberlistMessage.Merge(m, src) +} +func (m *MemberlistMessage) XXX_Size() int { + return m.Size() +} +func (m *MemberlistMessage) XXX_DiscardUnknown() { + xxx_messageInfo_MemberlistMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_MemberlistMessage proto.InternalMessageInfo + func init() { + proto.RegisterEnum("clusterpb.MemberlistMessage_Kind", MemberlistMessage_Kind_name, MemberlistMessage_Kind_value) proto.RegisterType((*Part)(nil), "clusterpb.Part") proto.RegisterType((*FullState)(nil), "clusterpb.FullState") + proto.RegisterType((*MemberlistMessage)(nil), "clusterpb.MemberlistMessage") } func init() { proto.RegisterFile("cluster.proto", fileDescriptor_3cfb3b8ec240c376) } var fileDescriptor_3cfb3b8ec240c376 = []byte{ - // 168 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4d, 0xce, 0x29, 0x2d, - 0x2e, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0x72, 0x0b, 0x92, 0xa4, - 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xa2, 0xfa, 0x20, 0x16, 0x44, 0x81, 0x92, 0x0e, 0x17, 0x4b, - 0x40, 0x62, 0x51, 0x89, 0x90, 0x00, 0x17, 0x73, 0x76, 0x6a, 0xa5, 0x04, 0xa3, 0x02, 0xa3, 0x06, - 0x67, 0x10, 0x88, 0x29, 0x24, 0xc4, 0xc5, 0x92, 0x92, 0x58, 0x92, 0x28, 0xc1, 0xa4, 0xc0, 0xa8, - 0xc1, 0x13, 0x04, 0x66, 0x2b, 0x59, 0x70, 0x71, 0xba, 0x95, 0xe6, 0xe4, 0x04, 0x97, 0x24, 0x96, - 0xa4, 0x0a, 0x69, 0x73, 0xb1, 0x16, 0x24, 0x16, 0x95, 0x14, 0x4b, 0x30, 0x2a, 0x30, 0x6b, 0x70, - 0x1b, 0xf1, 0xeb, 0xc1, 0xed, 0xd2, 0x03, 0x19, 0xe9, 0xc4, 0x72, 0xe2, 0x9e, 0x3c, 0x43, 0x10, - 0x44, 0x8d, 0x93, 0xc0, 0x89, 0x87, 0x72, 0x0c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, - 0xf8, 0xe0, 0x91, 0x1c, 0x63, 0x12, 0x1b, 0xd8, 0x01, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xfd, 0x3c, 0xdb, 0xe7, 0xb2, 0x00, 0x00, 0x00, + // 289 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xdf, 0x4a, 0xc3, 0x30, + 0x14, 0xc6, 0x17, 0x17, 0xa7, 0x3d, 0xfe, 0xab, 0xc1, 0x8b, 0xa0, 0x50, 0x67, 0xaf, 0x06, 0x4a, + 0x85, 0x89, 0xe0, 0xed, 0x26, 0xf3, 0x66, 0x14, 0x46, 0xb7, 0x7b, 0x49, 0x4d, 0x2c, 0x65, 0x6d, + 0x53, 0x92, 0x4c, 0xf0, 0xb9, 0x7c, 0x89, 0x5e, 0xfa, 0x04, 0xa2, 0x7d, 0x12, 0x49, 0x36, 0x45, + 0xf0, 0xee, 0x77, 0x0e, 0x5f, 0x7e, 0x1f, 0x27, 0x70, 0xf0, 0x54, 0xac, 0xb4, 0x11, 0x2a, 0xaa, + 0x95, 0x34, 0x92, 0x78, 0x9b, 0xb1, 0x4e, 0x4f, 0x4f, 0x32, 0x99, 0x49, 0xb7, 0xbd, 0xb6, 0xb4, + 0x0e, 0x84, 0x57, 0x80, 0x67, 0x4c, 0x19, 0xe2, 0x43, 0x77, 0x29, 0x5e, 0x29, 0xea, 0xa3, 0x81, + 0x97, 0x58, 0x24, 0x04, 0x30, 0x67, 0x86, 0xd1, 0xad, 0x3e, 0x1a, 0xec, 0x27, 0x8e, 0xc3, 0x3b, + 0xf0, 0x1e, 0x56, 0x45, 0x31, 0x37, 0xcc, 0x08, 0x72, 0x09, 0xdb, 0x35, 0x53, 0x46, 0x53, 0xd4, + 0xef, 0x0e, 0xf6, 0x86, 0x47, 0xd1, 0x6f, 0x57, 0x64, 0x95, 0x63, 0xdc, 0x7c, 0x9c, 0x77, 0x92, + 0x75, 0x26, 0x7c, 0x43, 0x70, 0x1c, 0x8b, 0x32, 0x15, 0xaa, 0xc8, 0xb5, 0x89, 0x85, 0xd6, 0x2c, + 0x13, 0x84, 0xc2, 0xce, 0x8b, 0x50, 0x3a, 0x97, 0xd5, 0xa6, 0xf9, 0x67, 0x24, 0xb7, 0x80, 0x97, + 0x79, 0xc5, 0x5d, 0xfb, 0xe1, 0xf0, 0xe2, 0x8f, 0xfb, 0x9f, 0x25, 0x9a, 0xe6, 0x15, 0x4f, 0x5c, + 0x9c, 0x9c, 0x81, 0xf7, 0xac, 0x64, 0xf9, 0xc8, 0x38, 0x57, 0xb4, 0xeb, 0x94, 0xbb, 0x76, 0x31, + 0xe2, 0x5c, 0xd9, 0x1b, 0x4b, 0x9d, 0x51, 0xec, 0x0e, 0xb2, 0x18, 0x06, 0x80, 0xed, 0x63, 0x02, + 0xd0, 0x9b, 0x2f, 0x92, 0xc9, 0x28, 0xf6, 0x3b, 0x96, 0x67, 0xa3, 0xfb, 0xe9, 0x64, 0xe1, 0xa3, + 0xb1, 0xdf, 0x7c, 0x05, 0x9d, 0xa6, 0x0d, 0xd0, 0x7b, 0x1b, 0xa0, 0xcf, 0x36, 0x40, 0x69, 0xcf, + 0x7d, 0xdb, 0xcd, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x3f, 0xca, 0x45, 0x68, 0x01, 0x00, + 0x00, } func (m *Part) Marshal() (dAtA []byte, err error) { @@ -209,6 +287,59 @@ func (m *FullState) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *MemberlistMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MemberlistMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MemberlistMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.Msg) > 0 { + i -= len(m.Msg) + copy(dAtA[i:], m.Msg) + i = encodeVarintCluster(dAtA, i, uint64(len(m.Msg))) + i-- + dAtA[i] = 0x22 + } + if len(m.FromAddr) > 0 { + i -= len(m.FromAddr) + copy(dAtA[i:], m.FromAddr) + i = encodeVarintCluster(dAtA, i, uint64(len(m.FromAddr))) + i-- + dAtA[i] = 0x1a + } + if m.Kind != 0 { + i = encodeVarintCluster(dAtA, i, uint64(m.Kind)) + i-- + dAtA[i] = 0x10 + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintCluster(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintCluster(dAtA []byte, offset int, v uint64) int { offset -= sovCluster(v) base := offset @@ -258,6 +389,33 @@ func (m *FullState) Size() (n int) { return n } +func (m *MemberlistMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Version) + if l > 0 { + n += 1 + l + sovCluster(uint64(l)) + } + if m.Kind != 0 { + n += 1 + sovCluster(uint64(m.Kind)) + } + l = len(m.FromAddr) + if l > 0 { + n += 1 + l + sovCluster(uint64(l)) + } + l = len(m.Msg) + if l > 0 { + n += 1 + l + sovCluster(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func sovCluster(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -466,6 +624,174 @@ func (m *FullState) Unmarshal(dAtA []byte) error { } return nil } +func (m *MemberlistMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCluster + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MemberlistMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MemberlistMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCluster + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCluster + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCluster + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType) + } + m.Kind = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCluster + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Kind |= MemberlistMessage_Kind(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FromAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCluster + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCluster + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCluster + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FromAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Msg", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCluster + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCluster + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCluster + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Msg = append(m.Msg[:0], dAtA[iNdEx:postIndex]...) + if m.Msg == nil { + m.Msg = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCluster(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCluster + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipCluster(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/cluster/clusterpb/cluster.proto b/cluster/clusterpb/cluster.proto index 5387a6f7..c34ae603 100644 --- a/cluster/clusterpb/cluster.proto +++ b/cluster/clusterpb/cluster.proto @@ -16,3 +16,14 @@ message Part { message FullState { repeated Part parts = 1 [(gogoproto.nullable) = false]; } + +message MemberlistMessage { + string version = 1; + enum Kind { + STREAM = 0; + PACKET = 1; + } + Kind kind = 2; + string from_addr = 3; + bytes msg = 4; +} diff --git a/cluster/connection_pool.go b/cluster/connection_pool.go new file mode 100644 index 00000000..03014323 --- /dev/null +++ b/cluster/connection_pool.go @@ -0,0 +1,84 @@ +// Copyright 2020 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cluster + +import ( + "crypto/tls" + "fmt" + "sync" + "time" + + lru "github.com/hashicorp/golang-lru" + "github.com/pkg/errors" +) + +const capacity = 1024 + +type connectionPool struct { + mtx sync.Mutex + cache *lru.Cache + tlsConfig *tls.Config +} + +func newConnectionPool(tlsClientCfg *tls.Config) (*connectionPool, error) { + cache, err := lru.NewWithEvict( + capacity, func(_ interface{}, value interface{}) { + conn, ok := value.(*tlsConn) + if ok { + _ = conn.Close() + } + }, + ) + if err != nil { + return nil, errors.Wrap(err, "failed to create new LRU") + } + return &connectionPool{ + cache: cache, + tlsConfig: tlsClientCfg, + }, nil +} + +// borrowConnection returns a *tlsConn from the pool. The connection does not +// need to be returned to the pool because each connection has its own locking. +func (pool *connectionPool) borrowConnection(addr string, timeout time.Duration) (*tlsConn, error) { + pool.mtx.Lock() + defer pool.mtx.Unlock() + if pool.cache == nil { + return nil, errors.New("connection pool closed") + } + key := fmt.Sprintf("%s/%d", addr, int64(timeout)) + value, exists := pool.cache.Get(key) + if exists { + conn, ok := value.(*tlsConn) + if ok && conn.alive() { + return conn, nil + } + } + conn, err := dialTLSConn(addr, timeout, pool.tlsConfig) + if err != nil { + return nil, err + } + pool.cache.Add(key, conn) + return conn, nil +} + +func (pool *connectionPool) shutdown() { + pool.mtx.Lock() + defer pool.mtx.Unlock() + if pool.cache == nil { + return + } + pool.cache.Purge() + pool.cache = nil +} diff --git a/cluster/testdata/certs/ca-config.json b/cluster/testdata/certs/ca-config.json new file mode 100644 index 00000000..a69dcd64 --- /dev/null +++ b/cluster/testdata/certs/ca-config.json @@ -0,0 +1,13 @@ +{ + "signing": { + "default": { + "expiry": "876000h" + }, + "profiles": { + "massl": { + "usages": ["signing", "key encipherment", "server auth", "client auth"], + "expiry": "876000h" + } + } + } +} diff --git a/cluster/testdata/certs/ca-csr.json b/cluster/testdata/certs/ca-csr.json new file mode 100644 index 00000000..0c52e02a --- /dev/null +++ b/cluster/testdata/certs/ca-csr.json @@ -0,0 +1,16 @@ +{ + "CN": "massl", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "L": "Melbourne", + "O": "massl", + "OU": "VIC", + "ST": "Victoria" + } + ] +} \ No newline at end of file diff --git a/cluster/testdata/certs/ca-key.pem b/cluster/testdata/certs/ca-key.pem new file mode 100644 index 00000000..fed98072 --- /dev/null +++ b/cluster/testdata/certs/ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuljDjKVGwlyiuKTSHc1QpoZPX9dbgwU/9113ctI8U/ZwMWLp +nZ4f/zVpf4LW5foM9zSEUGPiyJe/NaTZUOXkRBSIQ13QroK4OJ1XGacQKpTxewCb +ChESZEfKWEhnP/Y7BYc4z1Li6Dkxh4TIElHwOVe62jbhNnzYlr4evmSuiuItAc8u +hEYxncThPzmHEWPXKw8CFNhxCSYsjbb72UAIht0knMHQ7VXBX1VuuL0rolJBiToC +va+I6CjG0c6qfi9/BcPsuW6cNjmQnwTg6SaSoGO/5zgbxBgy9MZQEot88d1T2XH6 +rBANYsfojvyCXuytWnj04mvdAWwmFh0hhq+nxQIDAQABAoIBAQCwcL1vXUq7W4UD +OaRtbWrQ0dk0ETBnxT/E0y33fRJ8GZovWM2EXSVEuukSP+uEQ5elNYeWqo0fi3cT +ruvJSnMw9xPyXVDq+4C8slW3R1TqTK683VzvUizM4KC5qIyCpn1KBbgHrh6E7Sp1 +e4cIuaawVN3qIg5qThmx2YA4nBIcEt68q9cpy3NgEe+EQf44zM/St+y8kSkDUOVw +fNKX0WfZ/hPL1TAYpWiIgSf+m/V3d/1l/scvMYONcuSjXSORCyoeAWYtOQgf78wW +9j3kiBTaqDYCUZFnY/ltlZrm8ltAaKVJ0MmPKjVh8GJBXZp9fSVU8Y4ZIZRSeuBA +OoStHGAdAoGBAMluMIE33hGny2V0dNzW23D84eXQK38AsdP632jQeuzxBknItg45 +qAfzh8F8W10DQnSv5tj0bmOHfo0mG09bu9eo5nLLINOE7Ju/7ly/76RNJNJ4ADjx +JKZi/PpvfP+s/fzel0X3OPgA+CJKzUHuqlU4V9BLc7focZAYtaM2w7rHAoGBAOzU +eXpapkqYhbYRcsrVV57nZV0rLzsLVJBpJg2zC8un95ALrr0rlZfuPJfOCY/uuS1w +f8ixRz2MkRWGreLHy35NB4GV0sF9VPn1jMp9SuBNvO0JRUMWuDAdVe8SCjXadrOh ++m3yKJSkFKDchglUYnZKV1skgA/b9jjjnu2fvd0TAoGAVUTnFZxvzmuIp78fxWjS +5ka23hE8iHvjy4e00WsHzovNjKiBoQ35Orx16ItbJcm+dSUNhSQcItf104yhHPwJ +Tab7PvcMQ15OxzP9lJfPu27Iuqv/9Bro1+Kpkt5lPNqffk9AHGcmX54RbHrb3yBI +TOEYE14Nc3nbsRM0uQ3y13sCgYB5Om4QZpSWvKo9P4M+NqTKb3JglblwhOU9osVa +39ra3dkIgCJrLQM/KTEVF9+nMLDThLG0fqKT6/9cQHuECXet6Co+d/3RE6HK7Zmr +ESWh2ckqoMM2i0uvPWT+ooJdfL2kR/bUDtAc/jyc9yUZY3ufR4Cd4/o1pAfOqR1y +T4G1xwKBgQChE4VWawCVg2qanRjvZcdNk0zpZx4dxqqKYq/VHuSfjNLQixIZsgXT +xx9BHuORn6c/nurqEStLwN3BzbpPU/j6YjMUmTslSH2sKhHwWNYGBZC52aJiOOda +Bz6nAkihG0n2PjYt2T84w6FWHgLJuSsmiEVJcb+AOdyKh1MlzJiwMQ== +-----END RSA PRIVATE KEY----- diff --git a/cluster/testdata/certs/ca.csr b/cluster/testdata/certs/ca.csr new file mode 100644 index 00000000..f48c2c86 --- /dev/null +++ b/cluster/testdata/certs/ca.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpzCCAY8CAQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw +EAYDVQQHEwlNZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMx +DjAMBgNVBAMTBW1hc3NsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +uljDjKVGwlyiuKTSHc1QpoZPX9dbgwU/9113ctI8U/ZwMWLpnZ4f/zVpf4LW5foM +9zSEUGPiyJe/NaTZUOXkRBSIQ13QroK4OJ1XGacQKpTxewCbChESZEfKWEhnP/Y7 +BYc4z1Li6Dkxh4TIElHwOVe62jbhNnzYlr4evmSuiuItAc8uhEYxncThPzmHEWPX +Kw8CFNhxCSYsjbb72UAIht0knMHQ7VXBX1VuuL0rolJBiToCva+I6CjG0c6qfi9/ +BcPsuW6cNjmQnwTg6SaSoGO/5zgbxBgy9MZQEot88d1T2XH6rBANYsfojvyCXuyt +Wnj04mvdAWwmFh0hhq+nxQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAJFmooMt +TocElxCb3DGJTRUXxr4DqcATASIX35a2wV3MmPqUHHXr6BQkO/FRho66EsZf3DE/ +mumou01K+KByxgsmw04CACjSeZ2t/g6pAsDCKrx/BwL3tAo09lG2Y2Ah0BND2Cta +EZpTliU2MimZlk7UZb8VIXh2Tx56fZRoHLzO4U4+FY8ZR+tspxPRM7hLg/aUqA5D +zGj6kByX8aYjxsmQokP4rx/w2mz6vwt4cZ1pXwr0RderkMIh9Har/0k9X1WIAP61 +PNQx74qnaq+icjtN2+8gvJE/CJL/wfcwW6kQwEtX1xsTpnzyFaRoYpSPQrvkCtiW ++WzgnOh7RvKyAYI= +-----END CERTIFICATE REQUEST----- diff --git a/cluster/testdata/certs/ca.pem b/cluster/testdata/certs/ca.pem new file mode 100644 index 00000000..dbb0de80 --- /dev/null +++ b/cluster/testdata/certs/ca.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIUetcc8yLasSAxvyUWtgTVKR6jfBIwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN +ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT +BW1hc3NsMB4XDTIxMDUwNTE2MTYwMFoXDTI2MDUwNDE2MTYwMFowYjELMAkGA1UE +BhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlNZWxib3VybmUxDjAM +BgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMTBW1hc3NsMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuljDjKVGwlyiuKTSHc1QpoZPX9db +gwU/9113ctI8U/ZwMWLpnZ4f/zVpf4LW5foM9zSEUGPiyJe/NaTZUOXkRBSIQ13Q +roK4OJ1XGacQKpTxewCbChESZEfKWEhnP/Y7BYc4z1Li6Dkxh4TIElHwOVe62jbh +NnzYlr4evmSuiuItAc8uhEYxncThPzmHEWPXKw8CFNhxCSYsjbb72UAIht0knMHQ +7VXBX1VuuL0rolJBiToCva+I6CjG0c6qfi9/BcPsuW6cNjmQnwTg6SaSoGO/5zgb +xBgy9MZQEot88d1T2XH6rBANYsfojvyCXuytWnj04mvdAWwmFh0hhq+nxQIDAQAB +o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +d4DTElKq6gnGYDJZgJvC+4flrZAwDQYJKoZIhvcNAQELBQADggEBAHYaMxcyVdFP +QZJHDP6x+A1hHeZy/kaL2VPzUIOgt3ONEvjhsJblFjLaTWwXFJwxVHXRsQjqrURj +dD2VIkMbCJM4WN8odi6PdHscL0DwHKzwZDapQxsOUYcPBbR2EsBPRyWSoJmBnEyZ +0TANtyUcZNWc7/C4GG66SWypu1YRdtcaDiEOVkdetGWuo5bw1J7B7isQ4J4a3+Qn +iTKId/7wWBY95JS2xJuOeEjk/ty74ruSapk2Aip1GRSRXMDtD0XeO5dpxc6eIU3C +jbRpUGqCdSAfzxQTlxQAMEKdgRlt7lzMFX/PAXagDOMC9D/6APn5fquEV2d9wMWs +0N9UCwKftAU= +-----END CERTIFICATE----- diff --git a/cluster/testdata/certs/node1-csr.json b/cluster/testdata/certs/node1-csr.json new file mode 100644 index 00000000..1be91f95 --- /dev/null +++ b/cluster/testdata/certs/node1-csr.json @@ -0,0 +1,16 @@ +{ + "CN": "system:server", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "L": "Melbourne", + "O": "system:node1", + "OU": "massl", + "ST": "Victoria" + } + ] +} diff --git a/cluster/testdata/certs/node1-key.pem b/cluster/testdata/certs/node1-key.pem new file mode 100644 index 00000000..5e9209fe --- /dev/null +++ b/cluster/testdata/certs/node1-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA1b9bm4rvDtpYsqgtCC52+L535d4/Q2O10fWD2i2CfRXXfYJQ +5cr4AV2iqScFsJSs7KwyQde/c4VWj/vEA2/SJHZFBlknKdCcrgHVebrvnzm6Guze +ICutZSKocFXy9Kw+YmWuA64nHVfmSCKG07GhXhEsLsSCn4PTDYOiGAUm1GdSDxUp +8yUXec13Eb20mld0xE9kQnCnEWRnxMXtQJXoz9lpLc7DgXtN6nCXSG/CqdDPOduU +nmseaxyAGpAFnUmxcqUuYAJUQ1hUOJhk0RVSsLTmu+FGdOxk79AxmmKQ2z9l/GuA +VikVJGTxY4jRPezxHQ3bdqzzCIdJxTxLinftZQIDAQABAoIBADpxQtvphemau8vF +feKRycfDVEcOmF+VoL4SkgWSke4fjbbsbbAW6e59qp7zY3PfgtSHVIp6Mgek+oEN +xo9mAKAlkkPlFncxadWN/M921FPF1ePMxgMnzhYr/sAQUAikG76NrKGm+VzljrpE +bnbtR4DP0zPKWSjCQ2+bgTNuHSrPwUtEngVT6ugjfWU1RitlvjTsZ9hSuOSBlS7P +rjbQGaEh53PraDut8PIlF4wIF+nLeERFP/a6DC8Btpbv9P50YRosag6yU/G+OYX9 +spvBPvRJGrubslKnNRz9AcjbVd3QhL+Tm7mV7iakK918jLWb95Ro4WW+9lT6IAi6 +xRSOr9UCgYEA5wI3JhKkYa4PST7ALqmJSDkPH8+tctiEx+ovmnqBufFuLWFoO/rc +EOYslnaZM3UVCnhrFv7+LxezSI5DyQu8dBEzf0RMICvXUNBkGC7ZJQL428fjXPhX +8mZIoJ0ol4hbamr8yTYlK0vGTwqN1bDj71w6NszuN4ecN1cKNWsMbnMCgYEA7N8Y +MzHWNijMr7xZ1lXl4Ye9+kp3aTUjUYBBaxFr4bQ8y0fH48lzq3qOso5wgvp0DKYo +uemD5QKbo81LKoTRLa+nxPq0UqKm9FiSWmnrcxMuph989oZ1ZFHA2B+nvbuMTF8J +8sESclTSbgkG87DpycJOUwG3XAcXM+80pXuzJscCgYB+Dzxu/09KqoRW8PJIxGVQ +zypMrrS07iiPO2FcyCtQf8oi43vQ91Ttt91vAisZ5HNl8k5mDyJAKovANToSVOAy +6kwSz/9GswXdaMqmU7JVOyj4Lj0JN9AuS9ioJPrIrjVMfjORzYU8+i2uZlD94niP +3uE5lF0OWmdJ36qHefIftwKBgQDcPQZcO19H1iGS2FbTYeSvEK5ENM7YRG8FTXIF +4hnjrtjDzYb+tYVWEErznFrifYo/ZJMDYSqgWQ9reusDqqBvkR41mUDmgJMpJ91U +MZ2YzmIWVbqz4QrvbtAWY0Bsuh/VtpwiWQAUy+coJj6PgJOvY3m91h+tcm5RfHz/ +zIcjawKBgA6kDcOLOnWcvhP3XwtW5dcWlNuBBNEKna+kIT/5Vlrh91y2w7F54DNK +i0w5CZCpbTugJmZ67XLHnfongC7e2vAQ3atoT96RU4mf9614qs9LMtGAbnuCLB8+ +sT2rnaZKtzr83ensbYkbBxP/zmPBfFQ9FKcIYIA7En8zAIr2T3vJ +-----END RSA PRIVATE KEY----- diff --git a/cluster/testdata/certs/node1.csr b/cluster/testdata/certs/node1.csr new file mode 100644 index 00000000..c22bed88 --- /dev/null +++ b/cluster/testdata/certs/node1.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC5TCCAc0CAQAwczELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw +EAYDVQQHEwlNZWxib3VybmUxFTATBgNVBAoTDHN5c3RlbTpub2RlMTEOMAwGA1UE +CxMFbWFzc2wxFjAUBgNVBAMTDXN5c3RlbTpzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDVv1ubiu8O2liyqC0ILnb4vnfl3j9DY7XR9YPaLYJ9 +Fdd9glDlyvgBXaKpJwWwlKzsrDJB179zhVaP+8QDb9IkdkUGWScp0JyuAdV5uu+f +Oboa7N4gK61lIqhwVfL0rD5iZa4DricdV+ZIIobTsaFeESwuxIKfg9MNg6IYBSbU +Z1IPFSnzJRd5zXcRvbSaV3TET2RCcKcRZGfExe1AlejP2WktzsOBe03qcJdIb8Kp +0M8525Seax5rHIAakAWdSbFypS5gAlRDWFQ4mGTRFVKwtOa74UZ07GTv0DGaYpDb +P2X8a4BWKRUkZPFjiNE97PEdDdt2rPMIh0nFPEuKd+1lAgMBAAGgLTArBgkqhkiG +9w0BCQ4xHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B +AQsFAAOCAQEAW/tTyJaBfWtbC9hYUmhh8lxUztv2+WT4xaR/jdQ46sk/87vKuwI6 +4AkkGfiPLLqgW3xbQOwk5/ynRabttbsgTUHt744RtRFLzfcQKEBZoNPvrfHvmDil +YqHIOx2SJ5hzIBwVlVSBn50hdSSED1Ip22DaU8GukzuacB8+2rhg3MOWJbKVt5aR +03H4XkAynLS1FHNOraDIv1eT58D3l4hanrNOZIa0xAuChd25qLO/JHvU/3wccGUA +KNg3vGOy2Q8qVBrTFLn+yQHuOr/wSupXESO1jiI/h+txsBQnZ6oYfZnVJ+7o3Oln +3Hguw77aYeTAeZQPPbmJbDLegLG0ZC6RmA== +-----END CERTIFICATE REQUEST----- diff --git a/cluster/testdata/certs/node1.pem b/cluster/testdata/certs/node1.pem new file mode 100644 index 00000000..a7e05566 --- /dev/null +++ b/cluster/testdata/certs/node1.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIUbYMGwSgQF8iRZ5xmhflInj8VZ0owDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN +ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT +BW1hc3NsMCAXDTIxMDUwNTE2MTYwMFoYDzIxMjEwNDExMTYxNjAwWjBzMQswCQYD +VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEV +MBMGA1UEChMMc3lzdGVtOm5vZGUxMQ4wDAYDVQQLEwVtYXNzbDEWMBQGA1UEAxMN +c3lzdGVtOnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANW/ +W5uK7w7aWLKoLQgudvi+d+XeP0NjtdH1g9otgn0V132CUOXK+AFdoqknBbCUrOys +MkHXv3OFVo/7xANv0iR2RQZZJynQnK4B1Xm67585uhrs3iArrWUiqHBV8vSsPmJl +rgOuJx1X5kgihtOxoV4RLC7Egp+D0w2DohgFJtRnUg8VKfMlF3nNdxG9tJpXdMRP +ZEJwpxFkZ8TF7UCV6M/ZaS3Ow4F7Tepwl0hvwqnQzznblJ5rHmscgBqQBZ1JsXKl +LmACVENYVDiYZNEVUrC05rvhRnTsZO/QMZpikNs/ZfxrgFYpFSRk8WOI0T3s8R0N +23as8wiHScU8S4p37WUCAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE +FGprx5v+KrO4DeOtA6kps4BL/zKyMB8GA1UdIwQYMBaAFHeA0xJSquoJxmAyWYCb +wvuH5a2QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF +AAOCAQEAmWTdMLyWOrNAS0uY+u3FUV3Hm50xF1PfxbT6wK1hu6vH6B63E0o9K2/1 +U25Ie8Y2IzFocKMvbqC+mrY56G0bWoUlMONhthYqm8uTKtjlFO33A9I7WIT9Tw+B +nnwZZO7+Ljkd30qSzBinCjrIEx31Vq2pr54ungd8+wK8nfz/zdZnJcqxcN9zvCXB +GTE8yCuqGWKk/oDuIzVjr73U0QaWi+vThqJtBjhOIWQHHVJwbIyhuYzUaivgZPYB +8eKXWk4JH3eAcq5z5koNGyCcZd/k4WnvxZYxNBAkoQ6AWVfEMGOCaRjD1FTnMbpG +BW79ndJqLmn8OH+DeCnSWhTWxAgg+Q== +-----END CERTIFICATE----- diff --git a/cluster/testdata/certs/node2-csr.json b/cluster/testdata/certs/node2-csr.json new file mode 100644 index 00000000..ea0a8670 --- /dev/null +++ b/cluster/testdata/certs/node2-csr.json @@ -0,0 +1,16 @@ +{ + "CN": "system:server", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "L": "Melbourne", + "O": "system:node2", + "OU": "massl", + "ST": "Victoria" + } + ] +} diff --git a/cluster/testdata/certs/node2-key.pem b/cluster/testdata/certs/node2-key.pem new file mode 100644 index 00000000..3ab08b4d --- /dev/null +++ b/cluster/testdata/certs/node2-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtCtzT9vhRMTbhAg/pm8eBn+4IvVQeVqnHoEon9IKIx5fyvqS +Q6Ui3xSik9kJq5FSAa1mScajJwfB1o6ycaSP6n+Q88Py4v7q65n0stCHoJCH0uPw +MQyEhwX7nNilV9C4UZTyZ2StDdAjmMBHiN81EJAqH2d4Xtgrd/IIWhljSXm+aPbu +QjSz8BtR/7+MswrCdlJ8y6gWi020kt6GSHjmaxI1jStGvBxxksK86v3J97wfNwWY +7GJi70uBrvO0pk5bYckDzUTKeN1QGvBnZ8uDXs7pPysvftJr85GzX0iE9YLMDxO3 +qc/PlwCdxM8H6gHTTkLPizGZtpMF9Z497pW9YQIDAQABAoIBAFfQwdCPxHmnVbNB +7fwqNsFGKTLozMOJeuE0ZN+ZGZXKbTha70WHTLrcrO1RIRR9rTHiGXQmHEmez0zL +mpAnfHn4mWcm/9DCHTCehpVNbH3HVFxm+yB9EG9bbCsjsVtfASfKaGgauvp7k44V +UgiVeqDLE6zg2tunk3BQCOAZdbpOiXrdvoZiGx2Q4SMLPfzmfIyH4BUT836pLTmp +o6/yNiFqQWfCgjeEAOQor4TcdzYIT+3wP51HfAjhZKMIvmjwL16ov1/QpmWRD4ni +4svzYpeMYpl5OrZkKeDS4ZIQBGjxk+fzPmfFUbfVRSI2gDORsah8HoRVI4LnwKWn +7kQDv0ECgYEA6V+KVb8bPzCZNbroEZFdug6YtT4yv5Mj3/kpMTIvA3vtu02v8e7F +O56yT43QfUZA0Ar37O0HQ6mbpPsRE5RSr70i40RR+slMZVHX/AQViG7oQJGBijPt +1tFdLnb+1wSON3jYt2975Kw2IfgOXprWtEmL5zGuplEUjx9Lbdf1HjkCgYEAxaNe +XgXdAiWFoY4Qq6xBRO/WNZCdn3Ysqx6snCtDRilxeNyDoE/6x2Ma9/NRBtIiulAb +s09vDRfJKLbzocUhIn8BQ+GkbAS/A6+x2vcuGhK3F84xqZdbrCqvqdJS8K824jug +vUCfCBJlyNRDz8kEsN5odLM1xkij93Jv23HvGGkCgYEAptcz6ctfalSPI9eEs5KO +REbNK73UwBssaaISreYnsED4G5EVuUuvW8k/xxomtHj2OwWsa4ilSd1GtbL8aVf/ +qT35ZCrixP0GjeTuGXC+CDTp+8dKqggoAAzbpi1SUVwjZEsT/EhKdZgcdzqE42Ol +HWz7BQUCzEpo/U0tOtFKnxkCgYEAi05Vy8wyNbsg7/jlAzyNXPv4bxUaJTX00kDy +xbkw2BmKI/i6xprZVwUiEzdsG3SuicjBXahVzFLBtXMPUy1R57DBwYkgjgriYMTM +hlzIIBSk/aCXHMTVFwuXegoH8CJwexIwgHU2I0hkeiQ0EBfOuKRr2CYhdzvoZxhA +g9tQ/lECgYAjPYoXfNI3rHCWUmaD5eDJZpE0xuJeiiy5auojykdAc7vVapNaIyMK +G3EaU44RtXcSwH19TlH9UCm3MH1QiIwaBOzGcKj3Ut6ZyFKuWDUk4yqvps3uZU/h +h16Tp49Ja7/4LY1uuEngg1KMEiWgk5jiU7G0H9zrtEiTj9c3FDKDvg== +-----END RSA PRIVATE KEY----- diff --git a/cluster/testdata/certs/node2.csr b/cluster/testdata/certs/node2.csr new file mode 100644 index 00000000..0c2e406b --- /dev/null +++ b/cluster/testdata/certs/node2.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC5TCCAc0CAQAwczELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw +EAYDVQQHEwlNZWxib3VybmUxFTATBgNVBAoTDHN5c3RlbTpub2RlMjEOMAwGA1UE +CxMFbWFzc2wxFjAUBgNVBAMTDXN5c3RlbTpzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC0K3NP2+FExNuECD+mbx4Gf7gi9VB5WqcegSif0goj +Hl/K+pJDpSLfFKKT2QmrkVIBrWZJxqMnB8HWjrJxpI/qf5Dzw/Li/urrmfSy0Ieg +kIfS4/AxDISHBfuc2KVX0LhRlPJnZK0N0COYwEeI3zUQkCofZ3he2Ct38ghaGWNJ +eb5o9u5CNLPwG1H/v4yzCsJ2UnzLqBaLTbSS3oZIeOZrEjWNK0a8HHGSwrzq/cn3 +vB83BZjsYmLvS4Gu87SmTlthyQPNRMp43VAa8Gdny4Nezuk/Ky9+0mvzkbNfSIT1 +gswPE7epz8+XAJ3EzwfqAdNOQs+LMZm2kwX1nj3ulb1hAgMBAAGgLTArBgkqhkiG +9w0BCQ4xHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B +AQsFAAOCAQEARh0Pi36mNmyprU4j25GWNqQYCJ6cBGnaPeiwr8/F3rsGsF4LTQdP +xW2oBrEWyYRidNCkSMrPkcSiXu1Loy9APwSAXgJZWMYy0Ccdbd3P7dtGNOZkKaLA +QKntGA5E1YAbzNhlt7NviGpqZ49K2aOgcGBTnDZ7xDzmg4uo3tcHgzOCwarYZT8l +qVpc3jAyxRBOrxVKPZNFb4hAFvUm8k6/Etn5n4otN0JT3KGewbfQY50CxW5ShK52 +QCs2PmFMYHHmG11FD3W755MxzhL6UmMy20GUgWWthGmR1LugcBgDtWO/7bqqC9tT +XYDTDJ1j0g3Y0cvy2+kltrams4lGE3xs6g== +-----END CERTIFICATE REQUEST----- diff --git a/cluster/testdata/certs/node2.pem b/cluster/testdata/certs/node2.pem new file mode 100644 index 00000000..39a1dfd1 --- /dev/null +++ b/cluster/testdata/certs/node2.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIUex5xEYsDJPUg8idU0Sql2ixGdTwwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN +ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT +BW1hc3NsMCAXDTIxMDUwNTE2MTYwMFoYDzIxMjEwNDExMTYxNjAwWjBzMQswCQYD +VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEV +MBMGA1UEChMMc3lzdGVtOm5vZGUyMQ4wDAYDVQQLEwVtYXNzbDEWMBQGA1UEAxMN +c3lzdGVtOnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALQr +c0/b4UTE24QIP6ZvHgZ/uCL1UHlapx6BKJ/SCiMeX8r6kkOlIt8UopPZCauRUgGt +ZknGoycHwdaOsnGkj+p/kPPD8uL+6uuZ9LLQh6CQh9Lj8DEMhIcF+5zYpVfQuFGU +8mdkrQ3QI5jAR4jfNRCQKh9neF7YK3fyCFoZY0l5vmj27kI0s/AbUf+/jLMKwnZS +fMuoFotNtJLehkh45msSNY0rRrwccZLCvOr9yfe8HzcFmOxiYu9Lga7ztKZOW2HJ +A81EynjdUBrwZ2fLg17O6T8rL37Sa/ORs19IhPWCzA8Tt6nPz5cAncTPB+oB005C +z4sxmbaTBfWePe6VvWECAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE +FDNgivphLRqKzV8n29GJq6S2I+CQMB8GA1UdIwQYMBaAFHeA0xJSquoJxmAyWYCb +wvuH5a2QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF +AAOCAQEAnNG3nzycALGf+N8PuG4sUIkD+SYA1nOEgfD2KiGNyuTYHhGgFXTw8KzB +olH05VidldBvC0+pl5EqZAp9qdzpw6Z5Mb0gdoZY6TeKDUo022G3BHLMUGLp8y+i +KE6+awwgdJZ6vPbdnWAh7VM/HCUrGIIPmLFan13j/2RiMfaDxdMAowPmbVc8MLgA +JHI6pPo8D1DacEvMM09qGtwQEUoREOWJ/SzTWl1nc/IAS1yOL1LCyKLcoj/HWqjG +3LXficQ7rf+Cpn1GnrKwMziT0OLDLxOs/+5d3nFSLxqF1lpykhPPkmHOHnuY8sMX +Qdndn9QILdp5GNvqiVNQYcQa/gOb6g== +-----END CERTIFICATE----- diff --git a/cluster/testdata/tls_config_node1.yml b/cluster/testdata/tls_config_node1.yml new file mode 100644 index 00000000..42ab6953 --- /dev/null +++ b/cluster/testdata/tls_config_node1.yml @@ -0,0 +1,9 @@ +tls_server_config: + cert_file: "certs/node1.pem" + key_file: "certs/node1-key.pem" + client_ca_file: "certs/ca.pem" + client_auth_type: "VerifyClientCertIfGiven" +tls_client_config: + cert_file: "certs/node1.pem" + key_file: "certs/node1-key.pem" + ca_file: "certs/ca.pem" diff --git a/cluster/testdata/tls_config_node2.yml b/cluster/testdata/tls_config_node2.yml new file mode 100644 index 00000000..23d8d2be --- /dev/null +++ b/cluster/testdata/tls_config_node2.yml @@ -0,0 +1,9 @@ +tls_server_config: + cert_file: "certs/node2.pem" + key_file: "certs/node2-key.pem" + client_ca_file: "certs/ca.pem" + client_auth_type: "VerifyClientCertIfGiven" +tls_client_config: + cert_file: "certs/node2.pem" + key_file: "certs/node2-key.pem" + ca_file: "certs/ca.pem" diff --git a/cluster/tls_config.go b/cluster/tls_config.go new file mode 100644 index 00000000..4604a484 --- /dev/null +++ b/cluster/tls_config.go @@ -0,0 +1,45 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cluster + +import ( + "io/ioutil" + "path/filepath" + + "github.com/prometheus/common/config" + "github.com/prometheus/exporter-toolkit/web" + "gopkg.in/yaml.v2" +) + +type TLSTransportConfig struct { + TLSServerConfig *web.TLSStruct `yaml:"tls_server_config"` + TLSClientConfig *config.TLSConfig `yaml:"tls_client_config"` +} + +func GetTLSTransportConfig(configPath string) (*TLSTransportConfig, error) { + if configPath == "" { + return nil, nil + } + bytes, err := ioutil.ReadFile(configPath) + if err != nil { + return nil, err + } + cfg := &TLSTransportConfig{} + if err := yaml.UnmarshalStrict(bytes, cfg); err != nil { + return nil, err + } + cfg.TLSServerConfig.SetDirectory(filepath.Dir(configPath)) + cfg.TLSClientConfig.SetDirectory(filepath.Dir(configPath)) + return cfg, nil +} diff --git a/cluster/tls_connection.go b/cluster/tls_connection.go new file mode 100644 index 00000000..f5a3326f --- /dev/null +++ b/cluster/tls_connection.go @@ -0,0 +1,188 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cluster + +import ( + "bufio" + "crypto/tls" + "encoding/binary" + "io" + "net" + "sync" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/hashicorp/memberlist" + "github.com/pkg/errors" + + "github.com/prometheus/alertmanager/cluster/clusterpb" +) + +const ( + version = "v0.1.0" + uint32length = 4 +) + +// tlsConn wraps net.Conn with connection pooling data. +type tlsConn struct { + mtx sync.Mutex + connection net.Conn + live bool +} + +func dialTLSConn(addr string, timeout time.Duration, tlsConfig *tls.Config) (*tlsConn, error) { + dialer := &net.Dialer{Timeout: timeout} + conn, err := tls.DialWithDialer(dialer, network, addr, tlsConfig) + if err != nil { + return nil, err + } + + return &tlsConn{ + connection: conn, + live: true, + }, nil +} + +func rcvTLSConn(conn net.Conn) *tlsConn { + return &tlsConn{ + connection: conn, + live: true, + } +} + +// Write writes a byte array into the connection. It returns the number of bytes written and an error. +func (conn *tlsConn) Write(b []byte) (int, error) { + conn.mtx.Lock() + defer conn.mtx.Unlock() + n, err := conn.connection.Write(b) + + if err != nil { + conn.live = false + } + return n, err +} + +func (conn *tlsConn) alive() bool { + conn.mtx.Lock() + defer conn.mtx.Unlock() + return conn.live +} + +func (conn *tlsConn) getRawConn() net.Conn { + conn.mtx.Lock() + defer conn.mtx.Unlock() + raw := conn.connection + conn.live = false + conn.connection = nil + return raw +} + +// writePacket writes all the bytes in one operation so no concurrent write happens in between. +// It prefixes the message length. +func (conn *tlsConn) writePacket(fromAddr string, b []byte) error { + msg, err := proto.Marshal( + &clusterpb.MemberlistMessage{ + Version: version, + Kind: clusterpb.MemberlistMessage_PACKET, + FromAddr: fromAddr, + Msg: b, + }, + ) + if err != nil { + return errors.Wrap(err, "unable to marshal memeberlist packet message") + } + buf := make([]byte, uint32length, uint32length+len(msg)) + binary.LittleEndian.PutUint32(buf, uint32(len(msg))) + _, err = conn.Write(append(buf, msg...)) + return err +} + +// writeStream simply signals that this is a stream connection by sending the connection type. +func (conn *tlsConn) writeStream() error { + msg, err := proto.Marshal( + &clusterpb.MemberlistMessage{ + Version: version, + Kind: clusterpb.MemberlistMessage_STREAM, + }, + ) + if err != nil { + return errors.Wrap(err, "unable to marshal memeberlist stream message") + } + buf := make([]byte, uint32length, uint32length+len(msg)) + binary.LittleEndian.PutUint32(buf, uint32(len(msg))) + _, err = conn.Write(append(buf, msg...)) + return err +} + +// read returns a packet for packet connections or an error if there is one. +// It returns nothing if the connection is meant to be streamed. +func (conn *tlsConn) read() (*memberlist.Packet, error) { + if conn.connection == nil { + return nil, errors.New("nil connection") + } + + conn.mtx.Lock() + reader := bufio.NewReader(conn.connection) + lenBuf := make([]byte, uint32length) + _, err := io.ReadFull(reader, lenBuf) + if err != nil { + return nil, errors.Wrap(err, "error reading message length") + } + msgLen := binary.LittleEndian.Uint32(lenBuf) + msgBuf := make([]byte, msgLen) + _, err = io.ReadFull(reader, msgBuf) + conn.mtx.Unlock() + + if err != nil { + return nil, errors.Wrap(err, "error reading message") + } + pb := clusterpb.MemberlistMessage{} + err = proto.Unmarshal(msgBuf, &pb) + if err != nil { + return nil, errors.Wrap(err, "error parsing message") + } + if pb.Version != version { + return nil, errors.New("tls memberlist message version incompatible") + } + switch pb.Kind { + case clusterpb.MemberlistMessage_STREAM: + return nil, nil + case clusterpb.MemberlistMessage_PACKET: + return toPacket(pb) + default: + return nil, errors.New("could not read from either stream or packet channel") + } +} + +func toPacket(pb clusterpb.MemberlistMessage) (*memberlist.Packet, error) { + addr, err := net.ResolveTCPAddr(network, pb.FromAddr) + if err != nil { + return nil, errors.Wrap(err, "error parsing packet sender address") + } + return &memberlist.Packet{ + Buf: pb.Msg, + From: addr, + Timestamp: time.Now(), + }, nil +} + +func (conn *tlsConn) Close() error { + conn.mtx.Lock() + defer conn.mtx.Unlock() + conn.live = false + if conn.connection == nil { + return nil + } + return conn.connection.Close() +} diff --git a/cluster/tls_connection_test.go b/cluster/tls_connection_test.go new file mode 100644 index 00000000..93a40499 --- /dev/null +++ b/cluster/tls_connection_test.go @@ -0,0 +1,126 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cluster + +import ( + "errors" + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestWriteStream(t *testing.T) { + w, r := net.Pipe() + conn := &tlsConn{ + connection: w, + } + defer r.Close() + go func() { + conn.writeStream() + w.Close() + }() + packet, err := rcvTLSConn(r).read() + require.Nil(t, err) + require.Nil(t, packet) +} + +func TestWritePacket(t *testing.T) { + testCases := []struct { + fromAddr string + msg string + }{ + {fromAddr: "127.0.0.1:8001", msg: ""}, + {fromAddr: "10.0.0.4:9094", msg: "hello"}, + {fromAddr: "127.0.0.1:8001", msg: "0"}, + } + for _, tc := range testCases { + w, r := net.Pipe() + defer r.Close() + go func() { + conn := &tlsConn{connection: w} + conn.writePacket(tc.fromAddr, []byte(tc.msg)) + w.Close() + }() + packet, err := rcvTLSConn(r).read() + require.Nil(t, err) + require.Equal(t, tc.msg, string(packet.Buf)) + require.Equal(t, tc.fromAddr, packet.From.String()) + + } +} + +func TestRead_Nil(t *testing.T) { + packet, err := (&tlsConn{}).read() + require.Nil(t, packet) + require.NotNil(t, err) +} + +func TestTLSConn_Close(t *testing.T) { + testCases := []string{ + "foo", + "bar", + } + for _, tc := range testCases { + c := &tlsConn{ + connection: &mockConn{ + errMsg: tc, + }, + live: true, + } + err := c.Close() + require.Equal(t, errors.New(tc), err, tc) + require.False(t, c.alive()) + require.True(t, c.connection.(*mockConn).closed) + } +} + +type mockConn struct { + closed bool + errMsg string +} + +func (m *mockConn) Read(b []byte) (n int, err error) { + panic("implement me") +} + +func (m *mockConn) Write(b []byte) (n int, err error) { + panic("implement me") +} + +func (m *mockConn) Close() error { + m.closed = true + return errors.New(m.errMsg) +} + +func (m *mockConn) LocalAddr() net.Addr { + panic("implement me") +} + +func (m *mockConn) RemoteAddr() net.Addr { + panic("implement me") +} + +func (m *mockConn) SetDeadline(t time.Time) error { + panic("implement me") +} + +func (m *mockConn) SetReadDeadline(t time.Time) error { + panic("implement me") +} + +func (m *mockConn) SetWriteDeadline(t time.Time) error { + panic("implement me") +} diff --git a/cluster/tls_transport.go b/cluster/tls_transport.go new file mode 100644 index 00000000..ca8d04c2 --- /dev/null +++ b/cluster/tls_transport.go @@ -0,0 +1,346 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Forked from https://github.com/mxinden/memberlist-tls-transport. + +// Implements Transport interface so that all gossip communications occur via TLS over TCP. + +package cluster + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "strings" + "time" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/hashicorp/go-sockaddr" + "github.com/hashicorp/memberlist" + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + common "github.com/prometheus/common/config" + "github.com/prometheus/exporter-toolkit/web" +) + +const ( + metricNamespace = "alertmanager" + metricSubsystem = "tls_transport" + network = "tcp" +) + +// TLSTransport is a Transport implementation that uses TLS over TCP for both +// packet and stream operations. +type TLSTransport struct { + ctx context.Context + cancel context.CancelFunc + logger log.Logger + bindAddr string + bindPort int + done chan struct{} + listener net.Listener + packetCh chan *memberlist.Packet + streamCh chan net.Conn + connPool *connectionPool + tlsServerCfg *tls.Config + tlsClientCfg *tls.Config + + packetsSent prometheus.Counter + packetsRcvd prometheus.Counter + streamsSent prometheus.Counter + streamsRcvd prometheus.Counter + readErrs prometheus.Counter + writeErrs *prometheus.CounterVec +} + +// NewTLSTransport returns a TLS transport with the given configuration. +// On successful initialization, a tls listener will be created and listening. +// A valid bindAddr is required. If bindPort == 0, the system will assign +// a free port automatically. +func NewTLSTransport( + ctx context.Context, + logger log.Logger, + reg prometheus.Registerer, + bindAddr string, + bindPort int, + cfg *TLSTransportConfig, +) (*TLSTransport, error) { + if cfg == nil { + return nil, errors.New("must specify TLSTransportConfig") + } + tlsServerCfg, err := web.ConfigToTLSConfig(cfg.TLSServerConfig) + if err != nil { + return nil, errors.Wrap(err, "invalid TLS server config") + } + tlsClientCfg, err := common.NewTLSConfig(cfg.TLSClientConfig) + if err != nil { + return nil, errors.Wrap(err, "invalid TLS client config") + } + ip := net.ParseIP(bindAddr) + if ip == nil { + return nil, fmt.Errorf("invalid bind address \"%s\"", bindAddr) + } + addr := &net.TCPAddr{IP: ip, Port: bindPort} + listener, err := tls.Listen(network, addr.String(), tlsServerCfg) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to start TLS listener on %q port %d", bindAddr, bindPort)) + } + connPool, err := newConnectionPool(tlsClientCfg) + if err != nil { + return nil, errors.Wrap(err, "failed to initialize tls transport connection pool") + } + ctx, cancel := context.WithCancel(ctx) + t := &TLSTransport{ + ctx: ctx, + cancel: cancel, + logger: logger, + bindAddr: bindAddr, + bindPort: bindPort, + done: make(chan struct{}), + listener: listener, + packetCh: make(chan *memberlist.Packet), + streamCh: make(chan net.Conn), + connPool: connPool, + tlsServerCfg: tlsServerCfg, + tlsClientCfg: tlsClientCfg, + } + + t.registerMetrics(reg) + + go func() { + t.listen() + close(t.done) + }() + return t, nil +} + +// FinalAdvertiseAddr is given the user's configured values (which +// might be empty) and returns the desired IP and port to advertise to +// the rest of the cluster. +func (t *TLSTransport) FinalAdvertiseAddr(ip string, port int) (net.IP, int, error) { + var advertiseAddr net.IP + var advertisePort int + if ip != "" { + advertiseAddr = net.ParseIP(ip) + if advertiseAddr == nil { + return nil, 0, fmt.Errorf("failed to parse advertise address %q", ip) + } + + if ip4 := advertiseAddr.To4(); ip4 != nil { + advertiseAddr = ip4 + } + advertisePort = port + } else { + if t.bindAddr == "0.0.0.0" { + // Otherwise, if we're not bound to a specific IP, let's + // use a suitable private IP address. + var err error + ip, err = sockaddr.GetPrivateIP() + if err != nil { + return nil, 0, fmt.Errorf("failed to get interface addresses: %v", err) + } + if ip == "" { + return nil, 0, fmt.Errorf("no private IP address found, and explicit IP not provided") + } + + advertiseAddr = net.ParseIP(ip) + if advertiseAddr == nil { + return nil, 0, fmt.Errorf("failed to parse advertise address: %q", ip) + } + } else { + advertiseAddr = t.listener.Addr().(*net.TCPAddr).IP + } + advertisePort = t.GetAutoBindPort() + } + return advertiseAddr, advertisePort, nil +} + +// PacketCh returns a channel that can be read to receive incoming +// packets from other peers. +func (t *TLSTransport) PacketCh() <-chan *memberlist.Packet { + return t.packetCh +} + +// StreamCh returns a channel that can be read to handle incoming stream +// connections from other peers. +func (t *TLSTransport) StreamCh() <-chan net.Conn { + return t.streamCh +} + +// Shutdown is called when memberlist is shutting down; this gives the +// TLS Transport a chance to clean up the listener and other goroutines. +func (t *TLSTransport) Shutdown() error { + level.Debug(t.logger).Log("msg", "shutting down tls transport") + t.cancel() + err := t.listener.Close() + t.connPool.shutdown() + <-t.done + return err +} + +// WriteTo is a packet-oriented interface that borrows a connection +// from the pool, and writes to it. It also returns a timestamp of when +// the packet was written. +func (t *TLSTransport) WriteTo(b []byte, addr string) (time.Time, error) { + conn, err := t.connPool.borrowConnection(addr, DefaultTcpTimeout) + if err != nil { + t.writeErrs.WithLabelValues("packet").Inc() + return time.Now(), errors.Wrap(err, "failed to dial") + } + fromAddr := t.listener.Addr().String() + err = conn.writePacket(fromAddr, b) + if err != nil { + t.writeErrs.WithLabelValues("packet").Inc() + return time.Now(), errors.Wrap(err, "failed to write packet") + } + t.packetsSent.Add(float64(len(b))) + return time.Now(), nil +} + +// DialTimeout is used to create a connection that allows memberlist +// to perform two-way communications with a peer. +func (t *TLSTransport) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) { + conn, err := dialTLSConn(addr, timeout, t.tlsClientCfg) + if err != nil { + t.writeErrs.WithLabelValues("stream").Inc() + return nil, errors.Wrap(err, "failed to dial") + } + err = conn.writeStream() + netConn := conn.getRawConn() + if err != nil { + t.writeErrs.WithLabelValues("stream").Inc() + return netConn, errors.Wrap(err, "failed to create stream connection") + } + t.streamsSent.Inc() + return netConn, nil +} + +// GetAutoBindPort returns the bind port that was automatically given by the system +// if a bindPort of 0 was specified during instantiation. +func (t *TLSTransport) GetAutoBindPort() int { + return t.listener.Addr().(*net.TCPAddr).Port +} + +// listen starts up multiple handlers accepting concurrent connections. +func (t *TLSTransport) listen() { + for { + select { + case <-t.ctx.Done(): + + return + default: + conn, err := t.listener.Accept() + if err != nil { + // The error "use of closed network connection" is returned when the listener is closed. + // It is not exported in a more reasonable way. See https://github.com/golang/go/issues/4373. + if strings.Contains(err.Error(), "use of closed network connection") { + return + } + t.readErrs.Inc() + level.Debug(t.logger).Log("msg", "error accepting connection", "err", err) + + } else { + go t.handle(conn) + } + } + } +} + +func (t *TLSTransport) handle(conn net.Conn) { + for { + packet, err := rcvTLSConn(conn).read() + if err != nil { + level.Debug(t.logger).Log("msg", "error reading from connection", "err", err) + t.readErrs.Inc() + return + } + select { + case <-t.ctx.Done(): + return + default: + if packet != nil { + n := len(packet.Buf) + t.packetCh <- packet + t.packetsRcvd.Add(float64(n)) + } else { + t.streamCh <- conn + t.streamsRcvd.Inc() + return + } + } + } +} + +func (t *TLSTransport) registerMetrics(reg prometheus.Registerer) { + t.packetsSent = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: metricNamespace, + Subsystem: metricSubsystem, + Name: "packet_bytes_sent_total", + Help: "The number of packet bytes sent to outgoing connections (excluding internal metadata).", + }, + ) + t.packetsRcvd = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: metricNamespace, + Subsystem: metricSubsystem, + Name: "packet_bytes_received_total", + Help: "The number of packet bytes received from incoming connections (excluding internal metadata).", + }, + ) + t.streamsSent = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: metricNamespace, + Subsystem: metricSubsystem, + Name: "stream_connections_sent_total", + Help: "The number of stream connections sent.", + }, + ) + + t.streamsRcvd = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: metricNamespace, + Subsystem: metricSubsystem, + Name: "stream_connections_received_total", + Help: "The number of stream connections received.", + }, + ) + t.readErrs = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: metricNamespace, + Subsystem: metricSubsystem, + Name: "read_errors_total", + Help: "The number of errors encountered while reading from incoming connections.", + }, + ) + t.writeErrs = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: metricNamespace, + Subsystem: metricSubsystem, + Name: "write_errors_total", + Help: "The number of errors encountered while writing to outgoing connections.", + }, + []string{"connection_type"}, + ) + + if reg != nil { + reg.MustRegister(t.packetsSent) + reg.MustRegister(t.packetsRcvd) + reg.MustRegister(t.streamsSent) + reg.MustRegister(t.streamsRcvd) + reg.MustRegister(t.readErrs) + reg.MustRegister(t.writeErrs) + } +} diff --git a/cluster/tls_transport_test.go b/cluster/tls_transport_test.go new file mode 100644 index 00000000..3b4aca30 --- /dev/null +++ b/cluster/tls_transport_test.go @@ -0,0 +1,213 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cluster + +import ( + "bufio" + context2 "context" + "fmt" + "io" + "net" + "sync" + "testing" + "time" + + "github.com/go-kit/log" + "github.com/stretchr/testify/require" +) + +var logger = log.NewNopLogger() + +func TestNewTLSTransport(t *testing.T) { + testCases := []struct { + bindAddr string + bindPort int + tlsConfFile string + err string + }{ + {err: "must specify TLSTransportConfig"}, + {err: "invalid bind address \"\"", tlsConfFile: "testdata/tls_config_node1.yml"}, + {bindAddr: "abc123", err: "invalid bind address \"abc123\"", tlsConfFile: "testdata/tls_config_node1.yml"}, + {bindAddr: localhost, bindPort: 0, tlsConfFile: "testdata/tls_config_node1.yml"}, + {bindAddr: localhost, bindPort: 9094, tlsConfFile: "testdata/tls_config_node2.yml"}, + } + l := log.NewNopLogger() + for _, tc := range testCases { + cfg := mustTLSTransportConfig(tc.tlsConfFile) + transport, err := NewTLSTransport(context2.Background(), l, nil, tc.bindAddr, tc.bindPort, cfg) + if len(tc.err) > 0 { + require.Equal(t, tc.err, err.Error()) + require.Nil(t, transport) + } else { + require.Nil(t, err) + require.Equal(t, tc.bindAddr, transport.bindAddr) + require.Equal(t, tc.bindPort, transport.bindPort) + require.Equal(t, l, transport.logger) + require.NotNil(t, transport.listener) + transport.Shutdown() + } + } +} + +const localhost = "127.0.0.1" + +func TestFinalAdvertiseAddr(t *testing.T) { + testCases := []struct { + bindAddr string + bindPort int + inputIp string + inputPort int + expectedIp string + expectedPort int + expectedError string + }{ + {bindAddr: localhost, bindPort: 9094, inputIp: "10.0.0.5", inputPort: 54231, expectedIp: "10.0.0.5", expectedPort: 54231}, + {bindAddr: localhost, bindPort: 9093, inputIp: "invalid", inputPort: 54231, expectedError: "failed to parse advertise address \"invalid\""}, + {bindAddr: "0.0.0.0", bindPort: 0, inputIp: "", inputPort: 0, expectedIp: "random"}, + {bindAddr: localhost, bindPort: 0, inputIp: "", inputPort: 0, expectedIp: localhost}, + {bindAddr: localhost, bindPort: 9095, inputIp: "", inputPort: 0, expectedIp: localhost, expectedPort: 9095}, + } + for _, tc := range testCases { + tlsConf := mustTLSTransportConfig("testdata/tls_config_node1.yml") + transport, err := NewTLSTransport(context2.Background(), logger, nil, tc.bindAddr, tc.bindPort, tlsConf) + require.Nil(t, err) + ip, port, err := transport.FinalAdvertiseAddr(tc.inputIp, tc.inputPort) + if len(tc.expectedError) > 0 { + require.Equal(t, tc.expectedError, err.Error()) + } else { + require.Nil(t, err) + if tc.expectedPort == 0 { + require.True(t, tc.expectedPort < port) + require.True(t, uint32(port) <= uint32(1<<32-1)) + } else { + require.Equal(t, tc.expectedPort, port) + } + if tc.expectedIp == "random" { + require.NotNil(t, ip) + } else { + require.Equal(t, tc.expectedIp, ip.String()) + } + } + transport.Shutdown() + } +} + +func TestWriteTo(t *testing.T) { + tlsConf1 := mustTLSTransportConfig("testdata/tls_config_node1.yml") + t1, _ := NewTLSTransport(context2.Background(), logger, nil, "127.0.0.1", 0, tlsConf1) + defer t1.Shutdown() + + tlsConf2 := mustTLSTransportConfig("testdata/tls_config_node2.yml") + t2, _ := NewTLSTransport(context2.Background(), logger, nil, "127.0.0.1", 0, tlsConf2) + defer t2.Shutdown() + + from := fmt.Sprintf("%s:%d", t1.bindAddr, t1.GetAutoBindPort()) + to := fmt.Sprintf("%s:%d", t2.bindAddr, t2.GetAutoBindPort()) + sent := []byte(("test packet")) + _, err := t1.WriteTo(sent, to) + require.Nil(t, err) + packet := <-t2.PacketCh() + require.Equal(t, sent, packet.Buf) + require.Equal(t, from, packet.From.String()) +} + +func BenchmarkWriteTo(b *testing.B) { + tlsConf1 := mustTLSTransportConfig("testdata/tls_config_node1.yml") + t1, _ := NewTLSTransport(context2.Background(), logger, nil, "127.0.0.1", 0, tlsConf1) + defer t1.Shutdown() + + tlsConf2 := mustTLSTransportConfig("testdata/tls_config_node2.yml") + t2, _ := NewTLSTransport(context2.Background(), logger, nil, "127.0.0.1", 0, tlsConf2) + defer t2.Shutdown() + + b.ResetTimer() + from := fmt.Sprintf("%s:%d", t1.bindAddr, t1.GetAutoBindPort()) + to := fmt.Sprintf("%s:%d", t2.bindAddr, t2.GetAutoBindPort()) + sent := []byte(("test packet")) + + _, err := t1.WriteTo(sent, to) + require.Nil(b, err) + packet := <-t2.PacketCh() + + require.Equal(b, sent, packet.Buf) + require.Equal(b, from, packet.From.String()) +} + +func TestDialTimout(t *testing.T) { + tlsConf1 := mustTLSTransportConfig("testdata/tls_config_node1.yml") + t1, err := NewTLSTransport(context2.Background(), logger, nil, "127.0.0.1", 0, tlsConf1) + require.Nil(t, err) + defer t1.Shutdown() + + tlsConf2 := mustTLSTransportConfig("testdata/tls_config_node2.yml") + t2, err := NewTLSTransport(context2.Background(), logger, nil, "127.0.0.1", 0, tlsConf2) + require.Nil(t, err) + defer t2.Shutdown() + + addr := fmt.Sprintf("%s:%d", t2.bindAddr, t2.GetAutoBindPort()) + from, err := t1.DialTimeout(addr, 5*time.Second) + require.Nil(t, err) + defer from.Close() + + var to net.Conn + var wg sync.WaitGroup + wg.Add(1) + go func() { + to = <-t2.StreamCh() + wg.Done() + }() + + sent := []byte(("test stream")) + m, err := from.Write(sent) + require.Nil(t, err) + require.Greater(t, m, 0) + + wg.Wait() + + reader := bufio.NewReader(to) + buf := make([]byte, len(sent)) + n, err := io.ReadFull(reader, buf) + require.Nil(t, err) + require.Equal(t, len(sent), n) + require.Equal(t, sent, buf) +} + +type logWr struct { + bytes []byte +} + +func (l *logWr) Write(p []byte) (n int, err error) { + l.bytes = append(l.bytes, p...) + return len(p), nil +} + +func TestShutdown(t *testing.T) { + tlsConf1 := mustTLSTransportConfig("testdata/tls_config_node1.yml") + l := &logWr{} + t1, _ := NewTLSTransport(context2.Background(), log.NewLogfmtLogger(l), nil, "127.0.0.1", 0, tlsConf1) + // Sleeping to make sure listeners have started and can subsequently be shut down gracefully. + time.Sleep(500 * time.Millisecond) + err := t1.Shutdown() + require.Nil(t, err) + require.NotContains(t, string(l.bytes), "use of closed network connection") + require.Contains(t, string(l.bytes), "shutting down tls transport") +} + +func mustTLSTransportConfig(filename string) *TLSTransportConfig { + config, err := GetTLSTransportConfig(filename) + if err != nil { + panic(err) + } + return config +} diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index f0640c30..ab51a741 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -211,6 +211,7 @@ func run() int { settleTimeout = kingpin.Flag("cluster.settle-timeout", "Maximum time to wait for cluster connections to settle before evaluating notifications.").Default(cluster.DefaultPushPullInterval.String()).Duration() reconnectInterval = kingpin.Flag("cluster.reconnect-interval", "Interval between attempting to reconnect to lost peers.").Default(cluster.DefaultReconnectInterval.String()).Duration() peerReconnectTimeout = kingpin.Flag("cluster.reconnect-timeout", "Length of time to attempt to reconnect to a lost peer.").Default(cluster.DefaultReconnectTimeout.String()).Duration() + tlsConfigFile = kingpin.Flag("cluster.tls-config", "[EXPERIMENTAL] Path to config yaml file that can enable mutual TLS within the gossip protocol.").Default("").String() ) promlogflag.AddFlags(kingpin.CommandLine, &promlogConfig) @@ -231,6 +232,11 @@ func run() int { return 1 } + tlsTransportConfig, err := cluster.GetTLSTransportConfig(*tlsConfigFile) + if err != nil { + level.Error(logger).Log("msg", "unable to initialize TLS transport configuration for gossip mesh", "err", err) + return 1 + } var peer *cluster.Peer if *clusterBindAddr != "" { peer, err = cluster.Create( @@ -245,6 +251,7 @@ func run() int { *tcpTimeout, *probeTimeout, *probeInterval, + tlsTransportConfig, ) if err != nil { level.Error(logger).Log("msg", "unable to initialize gossip mesh", "err", err) diff --git a/docs/https.md b/docs/https.md index da9f3f97..a42efede 100644 --- a/docs/https.md +++ b/docs/https.md @@ -8,8 +8,9 @@ sort_rank: 11 Alertmanager supports basic authentication and TLS. This is **experimental** and might change in the future. -Currently TLS is only supported for the HTTP traffic. Gossip traffic does not -support encryption yet. +Currently TLS is supported for the HTTP traffic and gossip traffic. + +## HTTP Traffic To specify which web configuration file to load, use the `--web.config.file` flag. @@ -82,3 +83,63 @@ basic_auth_users: [ : ... ] ``` +## Gossip Traffic + +To specify whether to use mutual TLS for gossip, use the `--cluster.tls-config` flag. + +The server and client sides of the gossip are configurable. + +``` +tls_server_config: + # Certificate and key files for server to use to authenticate to client. + cert_file: + key_file: + + # Server policy for client authentication. Maps to ClientAuth Policies. + # For more detail on clientAuth options: + # https://golang.org/pkg/crypto/tls/#ClientAuthType + [ client_auth_type: | default = "NoClientCert" ] + + # CA certificate for client certificate authentication to the server. + [ client_ca_file: ] + + # Minimum TLS version that is acceptable. + [ min_version: | default = "TLS12" ] + + # Maximum TLS version that is acceptable. + [ max_version: | default = "TLS13" ] + + # List of supported cipher suites for TLS versions up to TLS 1.2. If empty, + # Go default cipher suites are used. Available cipher suites are documented + # in the go documentation: + # https://golang.org/pkg/crypto/tls/#pkg-constants + [ cipher_suites: + [ - ] ] + + # prefer_server_cipher_suites controls whether the server selects the + # client's most preferred ciphersuite, or the server's most preferred + # ciphersuite. If true then the server's preference, as expressed in + # the order of elements in cipher_suites, is used. + [ prefer_server_cipher_suites: | default = true ] + + # Elliptic curves that will be used in an ECDHE handshake, in preference + # order. Available curves are documented in the go documentation: + # https://golang.org/pkg/crypto/tls/#CurveID + [ curve_preferences: + [ - ] ] + +tls_client_config: + # Path to the CA certificate with which to validate the server certificate. + [ ca_file: ] + + # Certificate and key files for client cert authentication to the server. + [ cert_file: ] + [ key_file: ] + + # Server name extension to indicate the name of the server. + # http://tools.ietf.org/html/rfc4366#section-3.1 + [ server_name: ] + + # Disable validation of the server certificate. + [ insecure_skip_verify: | default = false] +``` diff --git a/examples/ha/tls/Makefile b/examples/ha/tls/Makefile new file mode 100644 index 00000000..ae8774aa --- /dev/null +++ b/examples/ha/tls/Makefile @@ -0,0 +1,27 @@ +# Based on https://github.com/wolfeidau/golang-massl/ + +.PHONY: start +start: + goreman start + +.PHONY: gen-certs +gen-certs: certs/ca.pem certs/node1.pem certs/node1-key.pem certs/node2.pem certs/node2-key.pem + +certs/ca.pem certs/ca-key.pem: certs/ca-csr.json + cd certs; cfssl gencert -initca ca-csr.json | cfssljson -bare ca + +certs/node1.pem certs/node1-key.pem: certs/ca-config.json certs/ca.pem certs/ca-key.pem certs/node1-csr.json + cd certs; cfssl gencert \ + -ca=ca.pem \ + -ca-key=ca-key.pem \ + -config=ca-config.json \ + -hostname=localhost,127.0.0.1 \ + -profile=massl node1-csr.json | cfssljson -bare node1 + +certs/node2.pem certs/node2-key.pem: certs/ca-config.json certs/ca.pem certs/ca-key.pem certs/node2-csr.json + cd certs; cfssl gencert \ + -ca=ca.pem \ + -ca-key=ca-key.pem \ + -config=ca-config.json \ + -hostname=localhost,127.0.0.1 \ + -profile=massl node2-csr.json | cfssljson -bare node2 diff --git a/examples/ha/tls/Procfile b/examples/ha/tls/Procfile new file mode 100644 index 00000000..218b23d0 --- /dev/null +++ b/examples/ha/tls/Procfile @@ -0,0 +1,7 @@ +a1: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a1 --web.listen-address=:9093 --cluster.listen-address=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node1.yml +a2: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a2 --web.listen-address=:9094 --cluster.listen-address=127.0.0.1:8002 --cluster.peer=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node2.yml +a3: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a3 --web.listen-address=:9095 --cluster.listen-address=127.0.0.1:8003 --cluster.peer=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node1.yml +a4: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a4 --web.listen-address=:9096 --cluster.listen-address=127.0.0.1:8004 --cluster.peer=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node2.yml +a5: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a5 --web.listen-address=:9097 --cluster.listen-address=127.0.0.1:8005 --cluster.peer=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node1.yml +a6: ./../../../alertmanager --log.level=debug --storage.path=$TMPDIR/a6 --web.listen-address=:9098 --cluster.listen-address=127.0.0.1:8006 --cluster.peer=127.0.0.1:8001 --config.file=../alertmanager.yml --cluster.tls-config=./tls_config_node2.yml +wh: go run ../../webhook/echo.go diff --git a/examples/ha/tls/README.md b/examples/ha/tls/README.md new file mode 100644 index 00000000..400eaa65 --- /dev/null +++ b/examples/ha/tls/README.md @@ -0,0 +1,18 @@ +# TLS Transport Config Example + +## Usage +1. Install dependencies: + 1. `go install github.com/cloudflare/cfssl/cmd/cfssl` + 2. `go install github.com/mattn/goreman` +2. Build Alertmanager (root of repository): + 1. `go mod download` + 1. `make build`. +2. `make start` (inside this directory). + +## Testing +1. Start the cluster (as explained above) +2. Navigate to one of the Alertmanager instances at `localhost:9093`. +3. Create a silence. +4. Navigate to the other Alertmanager instance at `localhost:9094`. +5. Observe that the silence created in the other Alertmanager instance has been synchronized over to this instance. +6. Repeat. diff --git a/examples/ha/tls/certs/ca-config.json b/examples/ha/tls/certs/ca-config.json new file mode 100644 index 00000000..a69dcd64 --- /dev/null +++ b/examples/ha/tls/certs/ca-config.json @@ -0,0 +1,13 @@ +{ + "signing": { + "default": { + "expiry": "876000h" + }, + "profiles": { + "massl": { + "usages": ["signing", "key encipherment", "server auth", "client auth"], + "expiry": "876000h" + } + } + } +} diff --git a/examples/ha/tls/certs/ca-csr.json b/examples/ha/tls/certs/ca-csr.json new file mode 100644 index 00000000..0c52e02a --- /dev/null +++ b/examples/ha/tls/certs/ca-csr.json @@ -0,0 +1,16 @@ +{ + "CN": "massl", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "L": "Melbourne", + "O": "massl", + "OU": "VIC", + "ST": "Victoria" + } + ] +} \ No newline at end of file diff --git a/examples/ha/tls/certs/ca-key.pem b/examples/ha/tls/certs/ca-key.pem new file mode 100644 index 00000000..fed98072 --- /dev/null +++ b/examples/ha/tls/certs/ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuljDjKVGwlyiuKTSHc1QpoZPX9dbgwU/9113ctI8U/ZwMWLp +nZ4f/zVpf4LW5foM9zSEUGPiyJe/NaTZUOXkRBSIQ13QroK4OJ1XGacQKpTxewCb +ChESZEfKWEhnP/Y7BYc4z1Li6Dkxh4TIElHwOVe62jbhNnzYlr4evmSuiuItAc8u +hEYxncThPzmHEWPXKw8CFNhxCSYsjbb72UAIht0knMHQ7VXBX1VuuL0rolJBiToC +va+I6CjG0c6qfi9/BcPsuW6cNjmQnwTg6SaSoGO/5zgbxBgy9MZQEot88d1T2XH6 +rBANYsfojvyCXuytWnj04mvdAWwmFh0hhq+nxQIDAQABAoIBAQCwcL1vXUq7W4UD +OaRtbWrQ0dk0ETBnxT/E0y33fRJ8GZovWM2EXSVEuukSP+uEQ5elNYeWqo0fi3cT +ruvJSnMw9xPyXVDq+4C8slW3R1TqTK683VzvUizM4KC5qIyCpn1KBbgHrh6E7Sp1 +e4cIuaawVN3qIg5qThmx2YA4nBIcEt68q9cpy3NgEe+EQf44zM/St+y8kSkDUOVw +fNKX0WfZ/hPL1TAYpWiIgSf+m/V3d/1l/scvMYONcuSjXSORCyoeAWYtOQgf78wW +9j3kiBTaqDYCUZFnY/ltlZrm8ltAaKVJ0MmPKjVh8GJBXZp9fSVU8Y4ZIZRSeuBA +OoStHGAdAoGBAMluMIE33hGny2V0dNzW23D84eXQK38AsdP632jQeuzxBknItg45 +qAfzh8F8W10DQnSv5tj0bmOHfo0mG09bu9eo5nLLINOE7Ju/7ly/76RNJNJ4ADjx +JKZi/PpvfP+s/fzel0X3OPgA+CJKzUHuqlU4V9BLc7focZAYtaM2w7rHAoGBAOzU +eXpapkqYhbYRcsrVV57nZV0rLzsLVJBpJg2zC8un95ALrr0rlZfuPJfOCY/uuS1w +f8ixRz2MkRWGreLHy35NB4GV0sF9VPn1jMp9SuBNvO0JRUMWuDAdVe8SCjXadrOh ++m3yKJSkFKDchglUYnZKV1skgA/b9jjjnu2fvd0TAoGAVUTnFZxvzmuIp78fxWjS +5ka23hE8iHvjy4e00WsHzovNjKiBoQ35Orx16ItbJcm+dSUNhSQcItf104yhHPwJ +Tab7PvcMQ15OxzP9lJfPu27Iuqv/9Bro1+Kpkt5lPNqffk9AHGcmX54RbHrb3yBI +TOEYE14Nc3nbsRM0uQ3y13sCgYB5Om4QZpSWvKo9P4M+NqTKb3JglblwhOU9osVa +39ra3dkIgCJrLQM/KTEVF9+nMLDThLG0fqKT6/9cQHuECXet6Co+d/3RE6HK7Zmr +ESWh2ckqoMM2i0uvPWT+ooJdfL2kR/bUDtAc/jyc9yUZY3ufR4Cd4/o1pAfOqR1y +T4G1xwKBgQChE4VWawCVg2qanRjvZcdNk0zpZx4dxqqKYq/VHuSfjNLQixIZsgXT +xx9BHuORn6c/nurqEStLwN3BzbpPU/j6YjMUmTslSH2sKhHwWNYGBZC52aJiOOda +Bz6nAkihG0n2PjYt2T84w6FWHgLJuSsmiEVJcb+AOdyKh1MlzJiwMQ== +-----END RSA PRIVATE KEY----- diff --git a/examples/ha/tls/certs/ca.csr b/examples/ha/tls/certs/ca.csr new file mode 100644 index 00000000..f48c2c86 --- /dev/null +++ b/examples/ha/tls/certs/ca.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpzCCAY8CAQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw +EAYDVQQHEwlNZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMx +DjAMBgNVBAMTBW1hc3NsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +uljDjKVGwlyiuKTSHc1QpoZPX9dbgwU/9113ctI8U/ZwMWLpnZ4f/zVpf4LW5foM +9zSEUGPiyJe/NaTZUOXkRBSIQ13QroK4OJ1XGacQKpTxewCbChESZEfKWEhnP/Y7 +BYc4z1Li6Dkxh4TIElHwOVe62jbhNnzYlr4evmSuiuItAc8uhEYxncThPzmHEWPX +Kw8CFNhxCSYsjbb72UAIht0knMHQ7VXBX1VuuL0rolJBiToCva+I6CjG0c6qfi9/ +BcPsuW6cNjmQnwTg6SaSoGO/5zgbxBgy9MZQEot88d1T2XH6rBANYsfojvyCXuyt +Wnj04mvdAWwmFh0hhq+nxQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAJFmooMt +TocElxCb3DGJTRUXxr4DqcATASIX35a2wV3MmPqUHHXr6BQkO/FRho66EsZf3DE/ +mumou01K+KByxgsmw04CACjSeZ2t/g6pAsDCKrx/BwL3tAo09lG2Y2Ah0BND2Cta +EZpTliU2MimZlk7UZb8VIXh2Tx56fZRoHLzO4U4+FY8ZR+tspxPRM7hLg/aUqA5D +zGj6kByX8aYjxsmQokP4rx/w2mz6vwt4cZ1pXwr0RderkMIh9Har/0k9X1WIAP61 +PNQx74qnaq+icjtN2+8gvJE/CJL/wfcwW6kQwEtX1xsTpnzyFaRoYpSPQrvkCtiW ++WzgnOh7RvKyAYI= +-----END CERTIFICATE REQUEST----- diff --git a/examples/ha/tls/certs/ca.pem b/examples/ha/tls/certs/ca.pem new file mode 100644 index 00000000..dbb0de80 --- /dev/null +++ b/examples/ha/tls/certs/ca.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIUetcc8yLasSAxvyUWtgTVKR6jfBIwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN +ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT +BW1hc3NsMB4XDTIxMDUwNTE2MTYwMFoXDTI2MDUwNDE2MTYwMFowYjELMAkGA1UE +BhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlNZWxib3VybmUxDjAM +BgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMTBW1hc3NsMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuljDjKVGwlyiuKTSHc1QpoZPX9db +gwU/9113ctI8U/ZwMWLpnZ4f/zVpf4LW5foM9zSEUGPiyJe/NaTZUOXkRBSIQ13Q +roK4OJ1XGacQKpTxewCbChESZEfKWEhnP/Y7BYc4z1Li6Dkxh4TIElHwOVe62jbh +NnzYlr4evmSuiuItAc8uhEYxncThPzmHEWPXKw8CFNhxCSYsjbb72UAIht0knMHQ +7VXBX1VuuL0rolJBiToCva+I6CjG0c6qfi9/BcPsuW6cNjmQnwTg6SaSoGO/5zgb +xBgy9MZQEot88d1T2XH6rBANYsfojvyCXuytWnj04mvdAWwmFh0hhq+nxQIDAQAB +o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +d4DTElKq6gnGYDJZgJvC+4flrZAwDQYJKoZIhvcNAQELBQADggEBAHYaMxcyVdFP +QZJHDP6x+A1hHeZy/kaL2VPzUIOgt3ONEvjhsJblFjLaTWwXFJwxVHXRsQjqrURj +dD2VIkMbCJM4WN8odi6PdHscL0DwHKzwZDapQxsOUYcPBbR2EsBPRyWSoJmBnEyZ +0TANtyUcZNWc7/C4GG66SWypu1YRdtcaDiEOVkdetGWuo5bw1J7B7isQ4J4a3+Qn +iTKId/7wWBY95JS2xJuOeEjk/ty74ruSapk2Aip1GRSRXMDtD0XeO5dpxc6eIU3C +jbRpUGqCdSAfzxQTlxQAMEKdgRlt7lzMFX/PAXagDOMC9D/6APn5fquEV2d9wMWs +0N9UCwKftAU= +-----END CERTIFICATE----- diff --git a/examples/ha/tls/certs/node1-csr.json b/examples/ha/tls/certs/node1-csr.json new file mode 100644 index 00000000..1be91f95 --- /dev/null +++ b/examples/ha/tls/certs/node1-csr.json @@ -0,0 +1,16 @@ +{ + "CN": "system:server", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "L": "Melbourne", + "O": "system:node1", + "OU": "massl", + "ST": "Victoria" + } + ] +} diff --git a/examples/ha/tls/certs/node1-key.pem b/examples/ha/tls/certs/node1-key.pem new file mode 100644 index 00000000..5e9209fe --- /dev/null +++ b/examples/ha/tls/certs/node1-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA1b9bm4rvDtpYsqgtCC52+L535d4/Q2O10fWD2i2CfRXXfYJQ +5cr4AV2iqScFsJSs7KwyQde/c4VWj/vEA2/SJHZFBlknKdCcrgHVebrvnzm6Guze +ICutZSKocFXy9Kw+YmWuA64nHVfmSCKG07GhXhEsLsSCn4PTDYOiGAUm1GdSDxUp +8yUXec13Eb20mld0xE9kQnCnEWRnxMXtQJXoz9lpLc7DgXtN6nCXSG/CqdDPOduU +nmseaxyAGpAFnUmxcqUuYAJUQ1hUOJhk0RVSsLTmu+FGdOxk79AxmmKQ2z9l/GuA +VikVJGTxY4jRPezxHQ3bdqzzCIdJxTxLinftZQIDAQABAoIBADpxQtvphemau8vF +feKRycfDVEcOmF+VoL4SkgWSke4fjbbsbbAW6e59qp7zY3PfgtSHVIp6Mgek+oEN +xo9mAKAlkkPlFncxadWN/M921FPF1ePMxgMnzhYr/sAQUAikG76NrKGm+VzljrpE +bnbtR4DP0zPKWSjCQ2+bgTNuHSrPwUtEngVT6ugjfWU1RitlvjTsZ9hSuOSBlS7P +rjbQGaEh53PraDut8PIlF4wIF+nLeERFP/a6DC8Btpbv9P50YRosag6yU/G+OYX9 +spvBPvRJGrubslKnNRz9AcjbVd3QhL+Tm7mV7iakK918jLWb95Ro4WW+9lT6IAi6 +xRSOr9UCgYEA5wI3JhKkYa4PST7ALqmJSDkPH8+tctiEx+ovmnqBufFuLWFoO/rc +EOYslnaZM3UVCnhrFv7+LxezSI5DyQu8dBEzf0RMICvXUNBkGC7ZJQL428fjXPhX +8mZIoJ0ol4hbamr8yTYlK0vGTwqN1bDj71w6NszuN4ecN1cKNWsMbnMCgYEA7N8Y +MzHWNijMr7xZ1lXl4Ye9+kp3aTUjUYBBaxFr4bQ8y0fH48lzq3qOso5wgvp0DKYo +uemD5QKbo81LKoTRLa+nxPq0UqKm9FiSWmnrcxMuph989oZ1ZFHA2B+nvbuMTF8J +8sESclTSbgkG87DpycJOUwG3XAcXM+80pXuzJscCgYB+Dzxu/09KqoRW8PJIxGVQ +zypMrrS07iiPO2FcyCtQf8oi43vQ91Ttt91vAisZ5HNl8k5mDyJAKovANToSVOAy +6kwSz/9GswXdaMqmU7JVOyj4Lj0JN9AuS9ioJPrIrjVMfjORzYU8+i2uZlD94niP +3uE5lF0OWmdJ36qHefIftwKBgQDcPQZcO19H1iGS2FbTYeSvEK5ENM7YRG8FTXIF +4hnjrtjDzYb+tYVWEErznFrifYo/ZJMDYSqgWQ9reusDqqBvkR41mUDmgJMpJ91U +MZ2YzmIWVbqz4QrvbtAWY0Bsuh/VtpwiWQAUy+coJj6PgJOvY3m91h+tcm5RfHz/ +zIcjawKBgA6kDcOLOnWcvhP3XwtW5dcWlNuBBNEKna+kIT/5Vlrh91y2w7F54DNK +i0w5CZCpbTugJmZ67XLHnfongC7e2vAQ3atoT96RU4mf9614qs9LMtGAbnuCLB8+ +sT2rnaZKtzr83ensbYkbBxP/zmPBfFQ9FKcIYIA7En8zAIr2T3vJ +-----END RSA PRIVATE KEY----- diff --git a/examples/ha/tls/certs/node1.csr b/examples/ha/tls/certs/node1.csr new file mode 100644 index 00000000..c22bed88 --- /dev/null +++ b/examples/ha/tls/certs/node1.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC5TCCAc0CAQAwczELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw +EAYDVQQHEwlNZWxib3VybmUxFTATBgNVBAoTDHN5c3RlbTpub2RlMTEOMAwGA1UE +CxMFbWFzc2wxFjAUBgNVBAMTDXN5c3RlbTpzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDVv1ubiu8O2liyqC0ILnb4vnfl3j9DY7XR9YPaLYJ9 +Fdd9glDlyvgBXaKpJwWwlKzsrDJB179zhVaP+8QDb9IkdkUGWScp0JyuAdV5uu+f +Oboa7N4gK61lIqhwVfL0rD5iZa4DricdV+ZIIobTsaFeESwuxIKfg9MNg6IYBSbU +Z1IPFSnzJRd5zXcRvbSaV3TET2RCcKcRZGfExe1AlejP2WktzsOBe03qcJdIb8Kp +0M8525Seax5rHIAakAWdSbFypS5gAlRDWFQ4mGTRFVKwtOa74UZ07GTv0DGaYpDb +P2X8a4BWKRUkZPFjiNE97PEdDdt2rPMIh0nFPEuKd+1lAgMBAAGgLTArBgkqhkiG +9w0BCQ4xHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B +AQsFAAOCAQEAW/tTyJaBfWtbC9hYUmhh8lxUztv2+WT4xaR/jdQ46sk/87vKuwI6 +4AkkGfiPLLqgW3xbQOwk5/ynRabttbsgTUHt744RtRFLzfcQKEBZoNPvrfHvmDil +YqHIOx2SJ5hzIBwVlVSBn50hdSSED1Ip22DaU8GukzuacB8+2rhg3MOWJbKVt5aR +03H4XkAynLS1FHNOraDIv1eT58D3l4hanrNOZIa0xAuChd25qLO/JHvU/3wccGUA +KNg3vGOy2Q8qVBrTFLn+yQHuOr/wSupXESO1jiI/h+txsBQnZ6oYfZnVJ+7o3Oln +3Hguw77aYeTAeZQPPbmJbDLegLG0ZC6RmA== +-----END CERTIFICATE REQUEST----- diff --git a/examples/ha/tls/certs/node1.pem b/examples/ha/tls/certs/node1.pem new file mode 100644 index 00000000..a7e05566 --- /dev/null +++ b/examples/ha/tls/certs/node1.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIUbYMGwSgQF8iRZ5xmhflInj8VZ0owDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN +ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT +BW1hc3NsMCAXDTIxMDUwNTE2MTYwMFoYDzIxMjEwNDExMTYxNjAwWjBzMQswCQYD +VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEV +MBMGA1UEChMMc3lzdGVtOm5vZGUxMQ4wDAYDVQQLEwVtYXNzbDEWMBQGA1UEAxMN +c3lzdGVtOnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANW/ +W5uK7w7aWLKoLQgudvi+d+XeP0NjtdH1g9otgn0V132CUOXK+AFdoqknBbCUrOys +MkHXv3OFVo/7xANv0iR2RQZZJynQnK4B1Xm67585uhrs3iArrWUiqHBV8vSsPmJl +rgOuJx1X5kgihtOxoV4RLC7Egp+D0w2DohgFJtRnUg8VKfMlF3nNdxG9tJpXdMRP +ZEJwpxFkZ8TF7UCV6M/ZaS3Ow4F7Tepwl0hvwqnQzznblJ5rHmscgBqQBZ1JsXKl +LmACVENYVDiYZNEVUrC05rvhRnTsZO/QMZpikNs/ZfxrgFYpFSRk8WOI0T3s8R0N +23as8wiHScU8S4p37WUCAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE +FGprx5v+KrO4DeOtA6kps4BL/zKyMB8GA1UdIwQYMBaAFHeA0xJSquoJxmAyWYCb +wvuH5a2QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF +AAOCAQEAmWTdMLyWOrNAS0uY+u3FUV3Hm50xF1PfxbT6wK1hu6vH6B63E0o9K2/1 +U25Ie8Y2IzFocKMvbqC+mrY56G0bWoUlMONhthYqm8uTKtjlFO33A9I7WIT9Tw+B +nnwZZO7+Ljkd30qSzBinCjrIEx31Vq2pr54ungd8+wK8nfz/zdZnJcqxcN9zvCXB +GTE8yCuqGWKk/oDuIzVjr73U0QaWi+vThqJtBjhOIWQHHVJwbIyhuYzUaivgZPYB +8eKXWk4JH3eAcq5z5koNGyCcZd/k4WnvxZYxNBAkoQ6AWVfEMGOCaRjD1FTnMbpG +BW79ndJqLmn8OH+DeCnSWhTWxAgg+Q== +-----END CERTIFICATE----- diff --git a/examples/ha/tls/certs/node2-csr.json b/examples/ha/tls/certs/node2-csr.json new file mode 100644 index 00000000..ea0a8670 --- /dev/null +++ b/examples/ha/tls/certs/node2-csr.json @@ -0,0 +1,16 @@ +{ + "CN": "system:server", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "L": "Melbourne", + "O": "system:node2", + "OU": "massl", + "ST": "Victoria" + } + ] +} diff --git a/examples/ha/tls/certs/node2-key.pem b/examples/ha/tls/certs/node2-key.pem new file mode 100644 index 00000000..3ab08b4d --- /dev/null +++ b/examples/ha/tls/certs/node2-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtCtzT9vhRMTbhAg/pm8eBn+4IvVQeVqnHoEon9IKIx5fyvqS +Q6Ui3xSik9kJq5FSAa1mScajJwfB1o6ycaSP6n+Q88Py4v7q65n0stCHoJCH0uPw +MQyEhwX7nNilV9C4UZTyZ2StDdAjmMBHiN81EJAqH2d4Xtgrd/IIWhljSXm+aPbu +QjSz8BtR/7+MswrCdlJ8y6gWi020kt6GSHjmaxI1jStGvBxxksK86v3J97wfNwWY +7GJi70uBrvO0pk5bYckDzUTKeN1QGvBnZ8uDXs7pPysvftJr85GzX0iE9YLMDxO3 +qc/PlwCdxM8H6gHTTkLPizGZtpMF9Z497pW9YQIDAQABAoIBAFfQwdCPxHmnVbNB +7fwqNsFGKTLozMOJeuE0ZN+ZGZXKbTha70WHTLrcrO1RIRR9rTHiGXQmHEmez0zL +mpAnfHn4mWcm/9DCHTCehpVNbH3HVFxm+yB9EG9bbCsjsVtfASfKaGgauvp7k44V +UgiVeqDLE6zg2tunk3BQCOAZdbpOiXrdvoZiGx2Q4SMLPfzmfIyH4BUT836pLTmp +o6/yNiFqQWfCgjeEAOQor4TcdzYIT+3wP51HfAjhZKMIvmjwL16ov1/QpmWRD4ni +4svzYpeMYpl5OrZkKeDS4ZIQBGjxk+fzPmfFUbfVRSI2gDORsah8HoRVI4LnwKWn +7kQDv0ECgYEA6V+KVb8bPzCZNbroEZFdug6YtT4yv5Mj3/kpMTIvA3vtu02v8e7F +O56yT43QfUZA0Ar37O0HQ6mbpPsRE5RSr70i40RR+slMZVHX/AQViG7oQJGBijPt +1tFdLnb+1wSON3jYt2975Kw2IfgOXprWtEmL5zGuplEUjx9Lbdf1HjkCgYEAxaNe +XgXdAiWFoY4Qq6xBRO/WNZCdn3Ysqx6snCtDRilxeNyDoE/6x2Ma9/NRBtIiulAb +s09vDRfJKLbzocUhIn8BQ+GkbAS/A6+x2vcuGhK3F84xqZdbrCqvqdJS8K824jug +vUCfCBJlyNRDz8kEsN5odLM1xkij93Jv23HvGGkCgYEAptcz6ctfalSPI9eEs5KO +REbNK73UwBssaaISreYnsED4G5EVuUuvW8k/xxomtHj2OwWsa4ilSd1GtbL8aVf/ +qT35ZCrixP0GjeTuGXC+CDTp+8dKqggoAAzbpi1SUVwjZEsT/EhKdZgcdzqE42Ol +HWz7BQUCzEpo/U0tOtFKnxkCgYEAi05Vy8wyNbsg7/jlAzyNXPv4bxUaJTX00kDy +xbkw2BmKI/i6xprZVwUiEzdsG3SuicjBXahVzFLBtXMPUy1R57DBwYkgjgriYMTM +hlzIIBSk/aCXHMTVFwuXegoH8CJwexIwgHU2I0hkeiQ0EBfOuKRr2CYhdzvoZxhA +g9tQ/lECgYAjPYoXfNI3rHCWUmaD5eDJZpE0xuJeiiy5auojykdAc7vVapNaIyMK +G3EaU44RtXcSwH19TlH9UCm3MH1QiIwaBOzGcKj3Ut6ZyFKuWDUk4yqvps3uZU/h +h16Tp49Ja7/4LY1uuEngg1KMEiWgk5jiU7G0H9zrtEiTj9c3FDKDvg== +-----END RSA PRIVATE KEY----- diff --git a/examples/ha/tls/certs/node2.csr b/examples/ha/tls/certs/node2.csr new file mode 100644 index 00000000..0c2e406b --- /dev/null +++ b/examples/ha/tls/certs/node2.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC5TCCAc0CAQAwczELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIw +EAYDVQQHEwlNZWxib3VybmUxFTATBgNVBAoTDHN5c3RlbTpub2RlMjEOMAwGA1UE +CxMFbWFzc2wxFjAUBgNVBAMTDXN5c3RlbTpzZXJ2ZXIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC0K3NP2+FExNuECD+mbx4Gf7gi9VB5WqcegSif0goj +Hl/K+pJDpSLfFKKT2QmrkVIBrWZJxqMnB8HWjrJxpI/qf5Dzw/Li/urrmfSy0Ieg +kIfS4/AxDISHBfuc2KVX0LhRlPJnZK0N0COYwEeI3zUQkCofZ3he2Ct38ghaGWNJ +eb5o9u5CNLPwG1H/v4yzCsJ2UnzLqBaLTbSS3oZIeOZrEjWNK0a8HHGSwrzq/cn3 +vB83BZjsYmLvS4Gu87SmTlthyQPNRMp43VAa8Gdny4Nezuk/Ky9+0mvzkbNfSIT1 +gswPE7epz8+XAJ3EzwfqAdNOQs+LMZm2kwX1nj3ulb1hAgMBAAGgLTArBgkqhkiG +9w0BCQ4xHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B +AQsFAAOCAQEARh0Pi36mNmyprU4j25GWNqQYCJ6cBGnaPeiwr8/F3rsGsF4LTQdP +xW2oBrEWyYRidNCkSMrPkcSiXu1Loy9APwSAXgJZWMYy0Ccdbd3P7dtGNOZkKaLA +QKntGA5E1YAbzNhlt7NviGpqZ49K2aOgcGBTnDZ7xDzmg4uo3tcHgzOCwarYZT8l +qVpc3jAyxRBOrxVKPZNFb4hAFvUm8k6/Etn5n4otN0JT3KGewbfQY50CxW5ShK52 +QCs2PmFMYHHmG11FD3W755MxzhL6UmMy20GUgWWthGmR1LugcBgDtWO/7bqqC9tT +XYDTDJ1j0g3Y0cvy2+kltrams4lGE3xs6g== +-----END CERTIFICATE REQUEST----- diff --git a/examples/ha/tls/certs/node2.pem b/examples/ha/tls/certs/node2.pem new file mode 100644 index 00000000..39a1dfd1 --- /dev/null +++ b/examples/ha/tls/certs/node2.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIUex5xEYsDJPUg8idU0Sql2ixGdTwwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQVUxETAPBgNVBAgTCFZpY3RvcmlhMRIwEAYDVQQHEwlN +ZWxib3VybmUxDjAMBgNVBAoTBW1hc3NsMQwwCgYDVQQLEwNWSUMxDjAMBgNVBAMT +BW1hc3NsMCAXDTIxMDUwNTE2MTYwMFoYDzIxMjEwNDExMTYxNjAwWjBzMQswCQYD +VQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExEjAQBgNVBAcTCU1lbGJvdXJuZTEV +MBMGA1UEChMMc3lzdGVtOm5vZGUyMQ4wDAYDVQQLEwVtYXNzbDEWMBQGA1UEAxMN +c3lzdGVtOnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALQr +c0/b4UTE24QIP6ZvHgZ/uCL1UHlapx6BKJ/SCiMeX8r6kkOlIt8UopPZCauRUgGt +ZknGoycHwdaOsnGkj+p/kPPD8uL+6uuZ9LLQh6CQh9Lj8DEMhIcF+5zYpVfQuFGU +8mdkrQ3QI5jAR4jfNRCQKh9neF7YK3fyCFoZY0l5vmj27kI0s/AbUf+/jLMKwnZS +fMuoFotNtJLehkh45msSNY0rRrwccZLCvOr9yfe8HzcFmOxiYu9Lga7ztKZOW2HJ +A81EynjdUBrwZ2fLg17O6T8rL37Sa/ORs19IhPWCzA8Tt6nPz5cAncTPB+oB005C +z4sxmbaTBfWePe6VvWECAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE +FDNgivphLRqKzV8n29GJq6S2I+CQMB8GA1UdIwQYMBaAFHeA0xJSquoJxmAyWYCb +wvuH5a2QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF +AAOCAQEAnNG3nzycALGf+N8PuG4sUIkD+SYA1nOEgfD2KiGNyuTYHhGgFXTw8KzB +olH05VidldBvC0+pl5EqZAp9qdzpw6Z5Mb0gdoZY6TeKDUo022G3BHLMUGLp8y+i +KE6+awwgdJZ6vPbdnWAh7VM/HCUrGIIPmLFan13j/2RiMfaDxdMAowPmbVc8MLgA +JHI6pPo8D1DacEvMM09qGtwQEUoREOWJ/SzTWl1nc/IAS1yOL1LCyKLcoj/HWqjG +3LXficQ7rf+Cpn1GnrKwMziT0OLDLxOs/+5d3nFSLxqF1lpykhPPkmHOHnuY8sMX +Qdndn9QILdp5GNvqiVNQYcQa/gOb6g== +-----END CERTIFICATE----- diff --git a/examples/ha/tls/tls_config_node1.yml b/examples/ha/tls/tls_config_node1.yml new file mode 100644 index 00000000..42ab6953 --- /dev/null +++ b/examples/ha/tls/tls_config_node1.yml @@ -0,0 +1,9 @@ +tls_server_config: + cert_file: "certs/node1.pem" + key_file: "certs/node1-key.pem" + client_ca_file: "certs/ca.pem" + client_auth_type: "VerifyClientCertIfGiven" +tls_client_config: + cert_file: "certs/node1.pem" + key_file: "certs/node1-key.pem" + ca_file: "certs/ca.pem" diff --git a/examples/ha/tls/tls_config_node2.yml b/examples/ha/tls/tls_config_node2.yml new file mode 100644 index 00000000..23d8d2be --- /dev/null +++ b/examples/ha/tls/tls_config_node2.yml @@ -0,0 +1,9 @@ +tls_server_config: + cert_file: "certs/node2.pem" + key_file: "certs/node2-key.pem" + client_ca_file: "certs/ca.pem" + client_auth_type: "VerifyClientCertIfGiven" +tls_client_config: + cert_file: "certs/node2.pem" + key_file: "certs/node2-key.pem" + ca_file: "certs/ca.pem" diff --git a/go.mod b/go.mod index 3846d5f3..2f9d87db 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/gogo/protobuf v1.3.2 github.com/hashicorp/go-sockaddr v1.0.2 github.com/hashicorp/go-uuid v1.0.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/memberlist v0.2.4 github.com/jessevdk/go-flags v1.5.0 github.com/kylelemons/godebug v1.1.0 diff --git a/go.sum b/go.sum index 1ac2f8a2..60f4a77e 100644 --- a/go.sum +++ b/go.sum @@ -306,8 +306,9 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/memberlist v0.2.4 h1:OOhYzSvFnkFQXm1ysE8RjXTHsqSRDyP4emusC9K7DYg= github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=