mirror of https://github.com/status-im/consul.git
Implement the insecure version of the Cluster.AutoConfig RPC endpoint
Right now this is only hooked into the insecure RPC server and requires JWT authorization. If no JWT authorizer is setup in the configuration then we inject a disabled “authorizer” to always report that JWT authorization is disabled.
This commit is contained in:
parent
1dba94311a
commit
9b01f9423c
|
@ -1403,6 +1403,18 @@ func (a *Agent) consulConfig() (*consul.Config, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// copy over auto config settings
|
||||
base.AutoConfigEnabled = a.config.AutoConfig.Enabled
|
||||
base.AutoConfigIntroToken = a.config.AutoConfig.IntroToken
|
||||
base.AutoConfigIntroTokenFile = a.config.AutoConfig.IntroTokenFile
|
||||
base.AutoConfigServerAddresses = a.config.AutoConfig.ServerAddresses
|
||||
base.AutoConfigDNSSANs = a.config.AutoConfig.DNSSANs
|
||||
base.AutoConfigIPSANs = a.config.AutoConfig.IPSANs
|
||||
base.AutoConfigAuthzEnabled = a.config.AutoConfig.Authorizer.Enabled
|
||||
base.AutoConfigAuthzAuthMethod = a.config.AutoConfig.Authorizer.AuthMethod
|
||||
base.AutoConfigAuthzClaimAssertions = a.config.AutoConfig.Authorizer.ClaimAssertions
|
||||
base.AutoConfigAuthzAllowReuse = a.config.AutoConfig.Authorizer.AllowReuse
|
||||
|
||||
// Setup the user event callback
|
||||
base.UserEventHandler = func(e serf.UserEvent) {
|
||||
select {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package agentpb
|
||||
|
||||
func (req *AutoConfigRequest) RequestDatacenter() string {
|
||||
return req.Datacenter
|
||||
}
|
||||
|
||||
func (req *AutoConfigRequest) IsRead() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (req *AutoConfigRequest) AllowStaleRead() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (req *AutoConfigRequest) TokenSecret() string {
|
||||
return req.ConsulToken
|
||||
}
|
||||
|
||||
func (req *AutoConfigRequest) SetTokenSecret(token string) {
|
||||
req.ConsulToken = token
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Code generated by protoc-gen-go-binary. DO NOT EDIT.
|
||||
// source: agent/agentpb/auto_config.proto
|
||||
|
||||
package agentpb
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *AutoConfigRequest) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *AutoConfigRequest) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *AutoConfigResponse) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *AutoConfigResponse) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
|
@ -0,0 +1,757 @@
|
|||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: agent/agentpb/auto_config.proto
|
||||
|
||||
package agentpb
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
config "github.com/hashicorp/consul/agent/agentpb/config"
|
||||
io "io"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// AutoConfigRequest is the data structure to be sent along with the
|
||||
// Cluster.AutoConfig RPC
|
||||
type AutoConfigRequest struct {
|
||||
// Datacenter is the local datacenter name. This wont actually be set by clients
|
||||
// but rather will be set by the servers to allow for forwarding to
|
||||
// the leader. If it ever happens to be set and differs from the local datacenters
|
||||
// name then an error should be returned.
|
||||
Datacenter string `protobuf:"bytes,1,opt,name=Datacenter,proto3" json:"Datacenter,omitempty"`
|
||||
// Node is the node name that the requester would like to assume
|
||||
// the identity of.
|
||||
Node string `protobuf:"bytes,2,opt,name=Node,proto3" json:"Node,omitempty"`
|
||||
// Segment is the network segment that the requester would like to join
|
||||
Segment string `protobuf:"bytes,4,opt,name=Segment,proto3" json:"Segment,omitempty"`
|
||||
// JWT is a signed JSON Web Token used to authorize the request
|
||||
JWT string `protobuf:"bytes,5,opt,name=JWT,proto3" json:"JWT,omitempty"`
|
||||
// ConsulToken is a Consul ACL token that the agent requesting the
|
||||
// configuration already has.
|
||||
ConsulToken string `protobuf:"bytes,6,opt,name=ConsulToken,proto3" json:"ConsulToken,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *AutoConfigRequest) Reset() { *m = AutoConfigRequest{} }
|
||||
func (m *AutoConfigRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AutoConfigRequest) ProtoMessage() {}
|
||||
func (*AutoConfigRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c842365210d144b0, []int{0}
|
||||
}
|
||||
func (m *AutoConfigRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *AutoConfigRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_AutoConfigRequest.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *AutoConfigRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AutoConfigRequest.Merge(m, src)
|
||||
}
|
||||
func (m *AutoConfigRequest) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *AutoConfigRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AutoConfigRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AutoConfigRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *AutoConfigRequest) GetDatacenter() string {
|
||||
if m != nil {
|
||||
return m.Datacenter
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AutoConfigRequest) GetNode() string {
|
||||
if m != nil {
|
||||
return m.Node
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AutoConfigRequest) GetSegment() string {
|
||||
if m != nil {
|
||||
return m.Segment
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AutoConfigRequest) GetJWT() string {
|
||||
if m != nil {
|
||||
return m.JWT
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AutoConfigRequest) GetConsulToken() string {
|
||||
if m != nil {
|
||||
return m.ConsulToken
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// AutoConfigResponse is the data structure sent in response to a Cluster.AutoConfig request
|
||||
type AutoConfigResponse struct {
|
||||
Config *config.Config `protobuf:"bytes,1,opt,name=Config,proto3" json:"Config,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *AutoConfigResponse) Reset() { *m = AutoConfigResponse{} }
|
||||
func (m *AutoConfigResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*AutoConfigResponse) ProtoMessage() {}
|
||||
func (*AutoConfigResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c842365210d144b0, []int{1}
|
||||
}
|
||||
func (m *AutoConfigResponse) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *AutoConfigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_AutoConfigResponse.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *AutoConfigResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AutoConfigResponse.Merge(m, src)
|
||||
}
|
||||
func (m *AutoConfigResponse) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *AutoConfigResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AutoConfigResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AutoConfigResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *AutoConfigResponse) GetConfig() *config.Config {
|
||||
if m != nil {
|
||||
return m.Config
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*AutoConfigRequest)(nil), "agentpb.AutoConfigRequest")
|
||||
proto.RegisterType((*AutoConfigResponse)(nil), "agentpb.AutoConfigResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("agent/agentpb/auto_config.proto", fileDescriptor_c842365210d144b0) }
|
||||
|
||||
var fileDescriptor_c842365210d144b0 = []byte{
|
||||
// 258 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0x4c, 0x4f, 0xcd,
|
||||
0x2b, 0xd1, 0x07, 0x93, 0x05, 0x49, 0xfa, 0x89, 0xa5, 0x25, 0xf9, 0xf1, 0xc9, 0xf9, 0x79, 0x69,
|
||||
0x99, 0xe9, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xec, 0x50, 0x29, 0x29, 0x45, 0x54, 0x95,
|
||||
0x10, 0x45, 0xfa, 0xc8, 0x6a, 0x95, 0xa6, 0x32, 0x72, 0x09, 0x3a, 0x96, 0x96, 0xe4, 0x3b, 0x83,
|
||||
0x05, 0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0xe4, 0xb8, 0xb8, 0x5c, 0x12, 0x4b, 0x12,
|
||||
0x93, 0x53, 0xf3, 0x4a, 0x52, 0x8b, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x90, 0x44, 0x84,
|
||||
0x84, 0xb8, 0x58, 0xfc, 0xf2, 0x53, 0x52, 0x25, 0x98, 0xc0, 0x32, 0x60, 0xb6, 0x90, 0x04, 0x17,
|
||||
0x7b, 0x70, 0x6a, 0x7a, 0x6e, 0x6a, 0x5e, 0x89, 0x04, 0x0b, 0x58, 0x18, 0xc6, 0x15, 0x12, 0xe0,
|
||||
0x62, 0xf6, 0x0a, 0x0f, 0x91, 0x60, 0x05, 0x8b, 0x82, 0x98, 0x42, 0x0a, 0x5c, 0xdc, 0xce, 0xf9,
|
||||
0x79, 0xc5, 0xa5, 0x39, 0x21, 0xf9, 0xd9, 0xa9, 0x79, 0x12, 0x6c, 0x60, 0x19, 0x64, 0x21, 0x25,
|
||||
0x1b, 0x2e, 0x21, 0x64, 0x67, 0x15, 0x17, 0xe4, 0xe7, 0x15, 0xa7, 0x0a, 0xa9, 0x71, 0xb1, 0x41,
|
||||
0x44, 0xc0, 0x6e, 0xe2, 0x36, 0xe2, 0xd3, 0x83, 0x7a, 0x06, 0xaa, 0x0e, 0x2a, 0xeb, 0x64, 0x7d,
|
||||
0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0xce, 0x78, 0x2c, 0xc7,
|
||||
0x10, 0xa5, 0x99, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x9f, 0x91, 0x58,
|
||||
0x9c, 0x91, 0x99, 0x9c, 0x5f, 0x54, 0x00, 0x0a, 0x8a, 0xe2, 0xd2, 0x1c, 0x7d, 0x94, 0x60, 0x4a,
|
||||
0x62, 0x03, 0x87, 0x8c, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x8c, 0xf1, 0x75, 0x68, 0x01,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *AutoConfigRequest) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *AutoConfigRequest) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Datacenter) > 0 {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintAutoConfig(dAtA, i, uint64(len(m.Datacenter)))
|
||||
i += copy(dAtA[i:], m.Datacenter)
|
||||
}
|
||||
if len(m.Node) > 0 {
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintAutoConfig(dAtA, i, uint64(len(m.Node)))
|
||||
i += copy(dAtA[i:], m.Node)
|
||||
}
|
||||
if len(m.Segment) > 0 {
|
||||
dAtA[i] = 0x22
|
||||
i++
|
||||
i = encodeVarintAutoConfig(dAtA, i, uint64(len(m.Segment)))
|
||||
i += copy(dAtA[i:], m.Segment)
|
||||
}
|
||||
if len(m.JWT) > 0 {
|
||||
dAtA[i] = 0x2a
|
||||
i++
|
||||
i = encodeVarintAutoConfig(dAtA, i, uint64(len(m.JWT)))
|
||||
i += copy(dAtA[i:], m.JWT)
|
||||
}
|
||||
if len(m.ConsulToken) > 0 {
|
||||
dAtA[i] = 0x32
|
||||
i++
|
||||
i = encodeVarintAutoConfig(dAtA, i, uint64(len(m.ConsulToken)))
|
||||
i += copy(dAtA[i:], m.ConsulToken)
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *AutoConfigResponse) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *AutoConfigResponse) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.Config != nil {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintAutoConfig(dAtA, i, uint64(m.Config.Size()))
|
||||
n1, err := m.Config.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n1
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeVarintAutoConfig(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *AutoConfigRequest) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Datacenter)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAutoConfig(uint64(l))
|
||||
}
|
||||
l = len(m.Node)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAutoConfig(uint64(l))
|
||||
}
|
||||
l = len(m.Segment)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAutoConfig(uint64(l))
|
||||
}
|
||||
l = len(m.JWT)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAutoConfig(uint64(l))
|
||||
}
|
||||
l = len(m.ConsulToken)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAutoConfig(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *AutoConfigResponse) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.Config != nil {
|
||||
l = m.Config.Size()
|
||||
n += 1 + l + sovAutoConfig(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovAutoConfig(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozAutoConfig(x uint64) (n int) {
|
||||
return sovAutoConfig(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *AutoConfigRequest) 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 ErrIntOverflowAutoConfig
|
||||
}
|
||||
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: AutoConfigRequest: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: AutoConfigRequest: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Datacenter", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAutoConfig
|
||||
}
|
||||
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 ErrInvalidLengthAutoConfig
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Datacenter = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Node", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAutoConfig
|
||||
}
|
||||
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 ErrInvalidLengthAutoConfig
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Node = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Segment", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAutoConfig
|
||||
}
|
||||
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 ErrInvalidLengthAutoConfig
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Segment = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 5:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field JWT", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAutoConfig
|
||||
}
|
||||
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 ErrInvalidLengthAutoConfig
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.JWT = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 6:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ConsulToken", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAutoConfig
|
||||
}
|
||||
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 ErrInvalidLengthAutoConfig
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.ConsulToken = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipAutoConfig(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
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 (m *AutoConfigResponse) 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 ErrIntOverflowAutoConfig
|
||||
}
|
||||
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: AutoConfigResponse: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: AutoConfigResponse: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAutoConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.Config == nil {
|
||||
m.Config = &config.Config{}
|
||||
}
|
||||
if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipAutoConfig(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthAutoConfig
|
||||
}
|
||||
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 skipAutoConfig(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAutoConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAutoConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAutoConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthAutoConfig
|
||||
}
|
||||
iNdEx += length
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthAutoConfig
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAutoConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipAutoConfig(dAtA[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthAutoConfig
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthAutoConfig = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowAutoConfig = fmt.Errorf("proto: integer overflow")
|
||||
)
|
|
@ -0,0 +1,36 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package agentpb;
|
||||
|
||||
option go_package = "github.com/hashicorp/consul/agent/agentpb";
|
||||
|
||||
import "agent/agentpb/config/config.proto";
|
||||
|
||||
// AutoConfigRequest is the data structure to be sent along with the
|
||||
// Cluster.AutoConfig RPC
|
||||
message AutoConfigRequest {
|
||||
// Datacenter is the local datacenter name. This wont actually be set by clients
|
||||
// but rather will be set by the servers to allow for forwarding to
|
||||
// the leader. If it ever happens to be set and differs from the local datacenters
|
||||
// name then an error should be returned.
|
||||
string Datacenter = 1;
|
||||
|
||||
// Node is the node name that the requester would like to assume
|
||||
// the identity of.
|
||||
string Node = 2;
|
||||
|
||||
// Segment is the network segment that the requester would like to join
|
||||
string Segment = 4;
|
||||
|
||||
// JWT is a signed JSON Web Token used to authorize the request
|
||||
string JWT = 5;
|
||||
|
||||
// ConsulToken is a Consul ACL token that the agent requesting the
|
||||
// configuration already has.
|
||||
string ConsulToken = 6;
|
||||
}
|
||||
|
||||
// AutoConfigResponse is the data structure sent in response to a Cluster.AutoConfig request
|
||||
message AutoConfigResponse {
|
||||
config.Config Config = 1;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
// Code generated by protoc-gen-go-binary. DO NOT EDIT.
|
||||
// source: agent/agentpb/config/config.proto
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *Config) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *Config) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *Gossip) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *Gossip) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *GossipEncryption) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *GossipEncryption) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *TLS) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *TLS) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *ACL) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *ACL) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *ACLTokens) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *ACLTokens) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *ACLServiceProviderToken) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *ACLServiceProviderToken) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *AutoEncrypt) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *AutoEncrypt) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,70 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package config;
|
||||
|
||||
option go_package = "github.com/hashicorp/consul/agent/agentpb/config";
|
||||
|
||||
message Config {
|
||||
string Datacenter = 1;
|
||||
string PrimaryDatacenter = 2;
|
||||
string NodeName = 3;
|
||||
string SegmentName = 4;
|
||||
ACL ACL = 5;
|
||||
AutoEncrypt AutoEncrypt = 6;
|
||||
Gossip Gossip = 7;
|
||||
TLS TLS = 8;
|
||||
}
|
||||
|
||||
message Gossip {
|
||||
GossipEncryption Encryption = 1;
|
||||
repeated string RetryJoinLAN = 2;
|
||||
}
|
||||
|
||||
message GossipEncryption {
|
||||
string Key = 1;
|
||||
bool VerifyIncoming = 2;
|
||||
bool VerifyOutgoing = 3;
|
||||
}
|
||||
|
||||
message TLS {
|
||||
bool VerifyOutgoing = 1;
|
||||
bool VerifyServerHostname = 2;
|
||||
string CipherSuites = 3;
|
||||
string MinVersion = 4;
|
||||
bool PreferServerCipherSuites = 5;
|
||||
}
|
||||
|
||||
message ACL {
|
||||
bool Enabled = 1;
|
||||
string PolicyTTL = 2;
|
||||
string RoleTTL = 3;
|
||||
string TokenTTL = 4;
|
||||
string DownPolicy = 5;
|
||||
string DefaultPolicy = 6;
|
||||
bool EnableKeyListPolicy = 7;
|
||||
ACLTokens Tokens = 8;
|
||||
string DisabledTTL = 9;
|
||||
bool EnableTokenPersistence = 10;
|
||||
bool MSPDisableBootstrap = 11;
|
||||
}
|
||||
|
||||
message ACLTokens {
|
||||
string Master = 1;
|
||||
string Replication = 2;
|
||||
string AgentMaster = 3;
|
||||
string Default = 4;
|
||||
string Agent = 5;
|
||||
repeated ACLServiceProviderToken ManagedServiceProvider = 6;
|
||||
}
|
||||
|
||||
message ACLServiceProviderToken {
|
||||
string AccessorID = 1;
|
||||
string SecretID = 2;
|
||||
}
|
||||
|
||||
message AutoEncrypt {
|
||||
bool TLS = 1;
|
||||
repeated string DNSSAN = 2;
|
||||
repeated string IPSAN = 3;
|
||||
bool AllowTLS = 4;
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
package consul
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/acl"
|
||||
"github.com/hashicorp/consul/agent/agentpb"
|
||||
"github.com/hashicorp/consul/agent/agentpb/config"
|
||||
"github.com/hashicorp/consul/agent/consul/authmethod/ssoauth"
|
||||
"github.com/hashicorp/consul/agent/metadata"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/lib"
|
||||
"github.com/hashicorp/consul/lib/template"
|
||||
"github.com/hashicorp/consul/tlsutil"
|
||||
bexpr "github.com/hashicorp/go-bexpr"
|
||||
)
|
||||
|
||||
type AutoConfigOptions struct {
|
||||
NodeName string
|
||||
SegmentName string
|
||||
}
|
||||
|
||||
type AutoConfigAuthorizer interface {
|
||||
// Authorizes the request and returns a struct containing the various
|
||||
// options for how to generate the configuration.
|
||||
Authorize(*agentpb.AutoConfigRequest) (AutoConfigOptions, error)
|
||||
}
|
||||
|
||||
type disabledAuthorizer struct{}
|
||||
|
||||
func (_ *disabledAuthorizer) Authorize(_ *agentpb.AutoConfigRequest) (AutoConfigOptions, error) {
|
||||
return AutoConfigOptions{}, fmt.Errorf("Auto Config is disabled")
|
||||
}
|
||||
|
||||
type jwtAuthorizer struct {
|
||||
validator *ssoauth.Validator
|
||||
allowReuse bool
|
||||
claimAssertions []string
|
||||
}
|
||||
|
||||
func (a *jwtAuthorizer) Authorize(req *agentpb.AutoConfigRequest) (AutoConfigOptions, error) {
|
||||
// perform basic JWT Authorization
|
||||
identity, err := a.validator.ValidateLogin(context.Background(), req.JWT)
|
||||
if err != nil {
|
||||
// TODO (autoconf) maybe we should add a more generic permission denied error not tied to the ACL package?
|
||||
return AutoConfigOptions{}, acl.PermissionDenied("Failed JWT authorization: %v", err)
|
||||
}
|
||||
|
||||
varMap := map[string]string{
|
||||
"node": req.Node,
|
||||
"segment": req.Segment,
|
||||
}
|
||||
|
||||
// TODO (autoconf) check for JWT reuse if configured to do so.
|
||||
for _, raw := range a.claimAssertions {
|
||||
// validate and fill any HIL
|
||||
filled, err := template.InterpolateHIL(raw, varMap, true)
|
||||
if err != nil {
|
||||
return AutoConfigOptions{}, fmt.Errorf("Failed to render claim assertion template %q: %w", raw, err)
|
||||
}
|
||||
|
||||
evaluator, err := bexpr.CreateEvaluatorForType(filled, nil, identity.SelectableFields)
|
||||
if err != nil {
|
||||
return AutoConfigOptions{}, fmt.Errorf("Failed to create evaluator for claim assertion %q: %w", filled, err)
|
||||
}
|
||||
|
||||
ok, err := evaluator.Evaluate(identity.SelectableFields)
|
||||
if err != nil {
|
||||
return AutoConfigOptions{}, fmt.Errorf("Failed to execute claim assertion %q: %w", filled, err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return AutoConfigOptions{}, acl.PermissionDenied("Failed JWT claim assertion")
|
||||
}
|
||||
}
|
||||
|
||||
return AutoConfigOptions{
|
||||
NodeName: req.Node,
|
||||
SegmentName: req.Segment,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Cluster endpoint is used for cluster configuration operations
|
||||
type Cluster struct {
|
||||
srv *Server
|
||||
|
||||
authorizer AutoConfigAuthorizer
|
||||
}
|
||||
|
||||
// updateTLSCertificatesInConfig will ensure that the TLS settings regarding how an agent is
|
||||
// made aware of its certificates are populated. This will only work if connect is enabled and
|
||||
// in some cases only if auto_encrypt is enabled on the servers. This endpoint has the option
|
||||
// to configure auto_encrypt or potentially in the future to generate the certificates inline.
|
||||
func (c *Cluster) updateTLSCertificatesInConfig(opts AutoConfigOptions, conf *config.Config) error {
|
||||
if c.srv.config.AutoEncryptAllowTLS {
|
||||
conf.AutoEncrypt = &config.AutoEncrypt{TLS: true}
|
||||
} else {
|
||||
conf.AutoEncrypt = &config.AutoEncrypt{TLS: false}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateACLtokensInConfig will configure all of the agents ACL settings and will populate
|
||||
// the configuration with an agent token usable for all default agent operations.
|
||||
func (c *Cluster) updateACLsInConfig(opts AutoConfigOptions, conf *config.Config) error {
|
||||
acl := &config.ACL{
|
||||
Enabled: c.srv.config.ACLsEnabled,
|
||||
PolicyTTL: c.srv.config.ACLPolicyTTL.String(),
|
||||
RoleTTL: c.srv.config.ACLRoleTTL.String(),
|
||||
TokenTTL: c.srv.config.ACLTokenTTL.String(),
|
||||
DisabledTTL: c.srv.config.ACLDisabledTTL.String(),
|
||||
DownPolicy: c.srv.config.ACLDownPolicy,
|
||||
DefaultPolicy: c.srv.config.ACLDefaultPolicy,
|
||||
EnableKeyListPolicy: c.srv.config.ACLEnableKeyListPolicy,
|
||||
}
|
||||
|
||||
// when ACLs are enabled we want to create a local token with a node identity
|
||||
if c.srv.config.ACLsEnabled {
|
||||
// we have to require local tokens or else it would require having these servers use a token with acl:write to make a
|
||||
// token create RPC to the servers in the primary DC.
|
||||
if !c.srv.LocalTokensEnabled() {
|
||||
return fmt.Errorf("Agent Auto Configuration requires local token usage to be enabled in this datacenter: %s", c.srv.config.Datacenter)
|
||||
}
|
||||
|
||||
// generate the accessor id
|
||||
accessor, err := lib.GenerateUUID(c.srv.checkTokenUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// generate the secret id
|
||||
secret, err := lib.GenerateUUID(c.srv.checkTokenUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set up the token
|
||||
token := structs.ACLToken{
|
||||
AccessorID: accessor,
|
||||
SecretID: secret,
|
||||
Description: fmt.Sprintf("Auto Config Token for Node %q", opts.NodeName),
|
||||
CreateTime: time.Now(),
|
||||
Local: true,
|
||||
NodeIdentities: []*structs.ACLNodeIdentity{
|
||||
{
|
||||
NodeName: opts.NodeName,
|
||||
Datacenter: c.srv.config.Datacenter,
|
||||
},
|
||||
},
|
||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||
}
|
||||
|
||||
req := structs.ACLTokenBatchSetRequest{
|
||||
Tokens: structs.ACLTokens{&token},
|
||||
CAS: false,
|
||||
}
|
||||
|
||||
// perform the request to mint the new token
|
||||
if _, err := c.srv.raftApplyMsgpack(structs.ACLTokenSetRequestType, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
acl.Tokens = &config.ACLTokens{Agent: secret}
|
||||
}
|
||||
|
||||
conf.ACL = acl
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateJoinAddressesInConfig determines the correct gossip endpoints that clients should
|
||||
// be connecting to for joining the cluster based on the segment given in the opts parameter.
|
||||
func (c *Cluster) updateJoinAddressesInConfig(opts AutoConfigOptions, conf *config.Config) error {
|
||||
members, err := c.srv.LANSegmentMembers(opts.SegmentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var joinAddrs []string
|
||||
for _, m := range members {
|
||||
if ok, _ := metadata.IsConsulServer(m); ok {
|
||||
serfAddr := net.TCPAddr{IP: m.Addr, Port: int(m.Port)}
|
||||
joinAddrs = append(joinAddrs, serfAddr.String())
|
||||
}
|
||||
}
|
||||
|
||||
if conf.Gossip == nil {
|
||||
conf.Gossip = &config.Gossip{}
|
||||
}
|
||||
|
||||
conf.Gossip.RetryJoinLAN = joinAddrs
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateGossipEncryptionInConfig will populate the gossip encryption configuration settings
|
||||
func (c *Cluster) updateGossipEncryptionInConfig(_ AutoConfigOptions, conf *config.Config) error {
|
||||
// Add gossip encryption settings if there is any key loaded
|
||||
memberlistConfig := c.srv.config.SerfLANConfig.MemberlistConfig
|
||||
if lanKeyring := memberlistConfig.Keyring; lanKeyring != nil {
|
||||
if conf.Gossip == nil {
|
||||
conf.Gossip = &config.Gossip{}
|
||||
}
|
||||
if conf.Gossip.Encryption == nil {
|
||||
conf.Gossip.Encryption = &config.GossipEncryption{}
|
||||
}
|
||||
|
||||
pk := lanKeyring.GetPrimaryKey()
|
||||
if len(pk) > 0 {
|
||||
conf.Gossip.Encryption.Key = base64.StdEncoding.EncodeToString(pk)
|
||||
}
|
||||
|
||||
conf.Gossip.Encryption.VerifyIncoming = memberlistConfig.GossipVerifyIncoming
|
||||
conf.Gossip.Encryption.VerifyOutgoing = memberlistConfig.GossipVerifyOutgoing
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateTLSSettingsInConfig will populate the TLS configuration settings but will not
|
||||
// populate leaf or ca certficiates.
|
||||
func (c *Cluster) updateTLSSettingsInConfig(_ AutoConfigOptions, conf *config.Config) error {
|
||||
// add in TLS configuration
|
||||
if conf.TLS == nil {
|
||||
conf.TLS = &config.TLS{}
|
||||
}
|
||||
conf.TLS.VerifyServerHostname = c.srv.tlsConfigurator.VerifyServerHostname()
|
||||
base := c.srv.tlsConfigurator.Base()
|
||||
conf.TLS.VerifyOutgoing = base.VerifyOutgoing
|
||||
conf.TLS.MinVersion = base.TLSMinVersion
|
||||
conf.TLS.PreferServerCipherSuites = base.PreferServerCipherSuites
|
||||
|
||||
var err error
|
||||
conf.TLS.CipherSuites, err = tlsutil.CipherString(base.CipherSuites)
|
||||
return err
|
||||
}
|
||||
|
||||
// baseConfig will populate the configuration with some base settings such as the
|
||||
// datacenter names, node name etc.
|
||||
func (c *Cluster) baseConfig(opts AutoConfigOptions, conf *config.Config) error {
|
||||
if opts.NodeName == "" {
|
||||
return fmt.Errorf("Cannot generate auto config response without a node name")
|
||||
}
|
||||
|
||||
conf.Datacenter = c.srv.config.Datacenter
|
||||
conf.PrimaryDatacenter = c.srv.config.PrimaryDatacenter
|
||||
conf.NodeName = opts.NodeName
|
||||
conf.SegmentName = opts.SegmentName
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type autoConfigUpdater func(c *Cluster, opts AutoConfigOptions, conf *config.Config) error
|
||||
|
||||
var (
|
||||
// variable holding the list of config updating functions to execute when generating
|
||||
// the auto config response. This will allow for more easily adding extra self-contained
|
||||
// configurators here in the future.
|
||||
autoConfigUpdaters []autoConfigUpdater = []autoConfigUpdater{
|
||||
(*Cluster).baseConfig,
|
||||
(*Cluster).updateJoinAddressesInConfig,
|
||||
(*Cluster).updateGossipEncryptionInConfig,
|
||||
(*Cluster).updateTLSSettingsInConfig,
|
||||
(*Cluster).updateACLsInConfig,
|
||||
(*Cluster).updateTLSCertificatesInConfig,
|
||||
}
|
||||
)
|
||||
|
||||
// AgentAutoConfig will authorize the incoming request and then generate the configuration
|
||||
// to push down to the client
|
||||
func (c *Cluster) AutoConfig(req *agentpb.AutoConfigRequest, resp *agentpb.AutoConfigResponse) error {
|
||||
// default the datacenter to our datacenter - agents do not have to specify this as they may not
|
||||
// yet know the datacenter name they are going to be in.
|
||||
if req.Datacenter == "" {
|
||||
req.Datacenter = c.srv.config.Datacenter
|
||||
}
|
||||
|
||||
// TODO (autoconf) Is performing auto configuration over the WAN really a bad idea?
|
||||
if req.Datacenter != c.srv.config.Datacenter {
|
||||
return fmt.Errorf("invalid datacenter %q - agent auto configuration cannot target a remote datacenter", req.Datacenter)
|
||||
}
|
||||
|
||||
// forward to the leader
|
||||
if done, err := c.srv.forward("Cluster.AutoConfig", req, req, resp); done {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO (autoconf) maybe panic instead?
|
||||
if c.authorizer == nil {
|
||||
return fmt.Errorf("No Auto Config authorizer is configured")
|
||||
}
|
||||
|
||||
// authorize the request with the configured authorizer
|
||||
opts, err := c.authorizer.Authorize(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf := &config.Config{}
|
||||
|
||||
// update all the configurations
|
||||
for _, configFn := range autoConfigUpdaters {
|
||||
if err := configFn(c, opts, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
resp.Config = conf
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,617 @@
|
|||
package consul
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/agent/agentpb"
|
||||
"github.com/hashicorp/consul/agent/agentpb/config"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/internal/go-sso/oidcauth/oidcauthtest"
|
||||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/tlsutil"
|
||||
"github.com/hashicorp/memberlist"
|
||||
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
func testJWTStandardClaims() jwt.Claims {
|
||||
now := time.Now()
|
||||
|
||||
return jwt.Claims{
|
||||
Subject: "consul",
|
||||
Issuer: "consul",
|
||||
Audience: jwt.Audience{"consul"},
|
||||
NotBefore: jwt.NewNumericDate(now.Add(-1 * time.Second)),
|
||||
Expiry: jwt.NewNumericDate(now.Add(10 * time.Minute)),
|
||||
}
|
||||
}
|
||||
|
||||
func signJWT(t *testing.T, privKey string, claims jwt.Claims, privateClaims interface{}) string {
|
||||
t.Helper()
|
||||
token, err := oidcauthtest.SignJWT(privKey, claims, privateClaims)
|
||||
require.NoError(t, err)
|
||||
return token
|
||||
}
|
||||
|
||||
func signJWTWithStandardClaims(t *testing.T, privKey string, claims interface{}) string {
|
||||
t.Helper()
|
||||
return signJWT(t, privKey, testJWTStandardClaims(), claims)
|
||||
}
|
||||
|
||||
// TestClusterAutoConfig is really an integration test of all the moving parts of the Cluster.AutoConfig RPC.
|
||||
// Full testing of the individual parts will not be done in this test:
|
||||
//
|
||||
// * Any implementations of the AutoConfigAuthorizer interface (although these test do use the jwtAuthorizer)
|
||||
// * Each of the individual config generation functions. These can be unit tested separately and many wont
|
||||
// require a running test server.
|
||||
func TestClusterAutoConfig(t *testing.T) {
|
||||
type testCase struct {
|
||||
request agentpb.AutoConfigRequest
|
||||
expected agentpb.AutoConfigResponse
|
||||
patchResponse func(t *testing.T, srv *Server, resp *agentpb.AutoConfigResponse)
|
||||
err string
|
||||
}
|
||||
|
||||
gossipKey := make([]byte, 32)
|
||||
// this is not cryptographic randomness and is not secure but for the sake of this test its all we need.
|
||||
n, err := rand.Read(gossipKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 32, n)
|
||||
|
||||
gossipKeyEncoded := base64.StdEncoding.EncodeToString(gossipKey)
|
||||
|
||||
// generate a test certificate for the server serving out the insecure RPC
|
||||
cert, key, cacert, err := testTLSCertificates("server.dc1.consul")
|
||||
require.NoError(t, err)
|
||||
|
||||
// generate a JWT signer
|
||||
pub, priv, err := oidcauthtest.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, altpriv, err := oidcauthtest.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := map[string]testCase{
|
||||
"wrong-datacenter": {
|
||||
request: agentpb.AutoConfigRequest{
|
||||
Datacenter: "no-such-dc",
|
||||
},
|
||||
err: `invalid datacenter "no-such-dc" - agent auto configuration cannot target a remote datacenter`,
|
||||
},
|
||||
"unverifiable": {
|
||||
request: agentpb.AutoConfigRequest{
|
||||
Node: "test-node",
|
||||
// this is signed using an incorrect private key
|
||||
JWT: signJWTWithStandardClaims(t, altpriv, map[string]interface{}{"consul_node_name": "test-node"}),
|
||||
},
|
||||
err: "Permission denied: Failed JWT authorization: no known key successfully validated the token signature",
|
||||
},
|
||||
"claim-assertion-failed": {
|
||||
request: agentpb.AutoConfigRequest{
|
||||
Node: "test-node",
|
||||
JWT: signJWTWithStandardClaims(t, priv, map[string]interface{}{"wrong_claim": "test-node"}),
|
||||
},
|
||||
err: "Permission denied: Failed JWT claim assertion",
|
||||
},
|
||||
"good": {
|
||||
request: agentpb.AutoConfigRequest{
|
||||
Node: "test-node",
|
||||
JWT: signJWTWithStandardClaims(t, priv, map[string]interface{}{"consul_node_name": "test-node"}),
|
||||
},
|
||||
expected: agentpb.AutoConfigResponse{
|
||||
Config: &config.Config{
|
||||
Datacenter: "dc1",
|
||||
PrimaryDatacenter: "dc1",
|
||||
NodeName: "test-node",
|
||||
AutoEncrypt: &config.AutoEncrypt{
|
||||
TLS: true,
|
||||
},
|
||||
ACL: &config.ACL{
|
||||
Enabled: true,
|
||||
PolicyTTL: "30s",
|
||||
TokenTTL: "30s",
|
||||
RoleTTL: "30s",
|
||||
DisabledTTL: "0s",
|
||||
DownPolicy: "extend-cache",
|
||||
DefaultPolicy: "deny",
|
||||
Tokens: &config.ACLTokens{
|
||||
Agent: "patched-secret",
|
||||
},
|
||||
},
|
||||
Gossip: &config.Gossip{
|
||||
Encryption: &config.GossipEncryption{
|
||||
Key: gossipKeyEncoded,
|
||||
VerifyIncoming: true,
|
||||
VerifyOutgoing: true,
|
||||
},
|
||||
},
|
||||
TLS: &config.TLS{
|
||||
VerifyOutgoing: true,
|
||||
VerifyServerHostname: true,
|
||||
MinVersion: "tls12",
|
||||
PreferServerCipherSuites: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
patchResponse: func(t *testing.T, srv *Server, resp *agentpb.AutoConfigResponse) {
|
||||
// we are expecting an ACL token but cannot check anything for equality
|
||||
// so here we check that it was set and overwrite it
|
||||
require.NotNil(t, resp.Config)
|
||||
require.NotNil(t, resp.Config.ACL)
|
||||
require.NotNil(t, resp.Config.ACL.Tokens)
|
||||
require.NotEmpty(t, resp.Config.ACL.Tokens.Agent)
|
||||
resp.Config.ACL.Tokens.Agent = "patched-secret"
|
||||
|
||||
// we don't know the expected join address until we start up the test server
|
||||
joinAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: srv.config.SerfLANConfig.MemberlistConfig.AdvertisePort}
|
||||
require.NotNil(t, resp.Config.Gossip)
|
||||
require.Equal(t, []string{joinAddr.String()}, resp.Config.Gossip.RetryJoinLAN)
|
||||
resp.Config.Gossip.RetryJoinLAN = nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, s, _ := testACLServerWithConfig(t, func(c *Config) {
|
||||
c.Domain = "consul"
|
||||
c.AutoConfigAuthzEnabled = true
|
||||
c.AutoConfigAuthzAuthMethod = structs.ACLAuthMethod{
|
||||
Name: "Auth Config Authorizer",
|
||||
Type: "jwt",
|
||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||
Config: map[string]interface{}{
|
||||
"BoundAudiences": []string{"consul"},
|
||||
"BoundIssuer": "consul",
|
||||
"JWTValidationPubKeys": []string{pub},
|
||||
"ClaimMappings": map[string]string{
|
||||
"consul_node_name": "node",
|
||||
},
|
||||
},
|
||||
}
|
||||
c.AutoConfigAuthzClaimAssertions = []string{
|
||||
`value.node == "${node}"`,
|
||||
}
|
||||
c.AutoConfigAuthzAllowReuse = true
|
||||
|
||||
cafile := path.Join(c.DataDir, "cacert.pem")
|
||||
err := ioutil.WriteFile(cafile, []byte(cacert), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
certfile := path.Join(c.DataDir, "cert.pem")
|
||||
err = ioutil.WriteFile(certfile, []byte(cert), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
keyfile := path.Join(c.DataDir, "key.pem")
|
||||
err = ioutil.WriteFile(keyfile, []byte(key), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.CAFile = cafile
|
||||
c.CertFile = certfile
|
||||
c.KeyFile = keyfile
|
||||
c.VerifyOutgoing = true
|
||||
c.VerifyIncoming = true
|
||||
c.VerifyServerHostname = true
|
||||
c.TLSMinVersion = "tls12"
|
||||
c.TLSPreferServerCipherSuites = true
|
||||
|
||||
c.ConnectEnabled = true
|
||||
c.AutoEncryptAllowTLS = true
|
||||
c.SerfLANConfig.MemberlistConfig.GossipVerifyIncoming = true
|
||||
c.SerfLANConfig.MemberlistConfig.GossipVerifyOutgoing = true
|
||||
|
||||
keyring, err := memberlist.NewKeyring(nil, gossipKey)
|
||||
require.NoError(t, err)
|
||||
c.SerfLANConfig.MemberlistConfig.Keyring = keyring
|
||||
}, false)
|
||||
|
||||
conf := tlsutil.Config{
|
||||
CAFile: s.config.CAFile,
|
||||
VerifyServerHostname: s.config.VerifyServerHostname,
|
||||
VerifyOutgoing: s.config.VerifyOutgoing,
|
||||
Domain: s.config.Domain,
|
||||
}
|
||||
codec, err := insecureRPCClient(s, conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
waitForLeaderEstablishment(t, s)
|
||||
|
||||
for testName, tcase := range cases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
var reply agentpb.AutoConfigResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "Cluster.AutoConfig", &tcase.request, &reply)
|
||||
if tcase.err != "" {
|
||||
testutil.RequireErrorContains(t, err, tcase.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
if tcase.patchResponse != nil {
|
||||
tcase.patchResponse(t, s, &reply)
|
||||
}
|
||||
require.Equal(t, tcase.expected, reply)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterAutoConfig_baseConfig(t *testing.T) {
|
||||
type testCase struct {
|
||||
serverConfig Config
|
||||
opts AutoConfigOptions
|
||||
expected config.Config
|
||||
err string
|
||||
}
|
||||
|
||||
cases := map[string]testCase{
|
||||
"ok": {
|
||||
serverConfig: Config{
|
||||
Datacenter: "oSWzfhnU",
|
||||
PrimaryDatacenter: "53XO9mx4",
|
||||
},
|
||||
opts: AutoConfigOptions{
|
||||
NodeName: "lBdc0lsH",
|
||||
SegmentName: "HZiwlWpi",
|
||||
},
|
||||
expected: config.Config{
|
||||
Datacenter: "oSWzfhnU",
|
||||
PrimaryDatacenter: "53XO9mx4",
|
||||
NodeName: "lBdc0lsH",
|
||||
SegmentName: "HZiwlWpi",
|
||||
},
|
||||
},
|
||||
"no-node-name": {
|
||||
serverConfig: Config{
|
||||
Datacenter: "oSWzfhnU",
|
||||
PrimaryDatacenter: "53XO9mx4",
|
||||
},
|
||||
err: "Cannot generate auto config response without a node name",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tcase := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cluster := Cluster{
|
||||
srv: &Server{
|
||||
config: &tcase.serverConfig,
|
||||
},
|
||||
}
|
||||
|
||||
var actual config.Config
|
||||
err := cluster.baseConfig(tcase.opts, &actual)
|
||||
if tcase.err == "" {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tcase.expected, actual)
|
||||
} else {
|
||||
testutil.RequireErrorContains(t, err, tcase.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClusterAutoConfig_updateTLSSettingsInConfig(t *testing.T) {
|
||||
_, _, cacert, err := testTLSCertificates("server.dc1.consul")
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := testutil.TempDir(t, "auto-config-tls-settings")
|
||||
t.Cleanup(func() { os.RemoveAll(dir) })
|
||||
|
||||
cafile := path.Join(dir, "cacert.pem")
|
||||
err = ioutil.WriteFile(cafile, []byte(cacert), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
parseCiphers := func(t *testing.T, cipherStr string) []uint16 {
|
||||
t.Helper()
|
||||
ciphers, err := tlsutil.ParseCiphers(cipherStr)
|
||||
require.NoError(t, err)
|
||||
return ciphers
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
tlsConfig tlsutil.Config
|
||||
expected config.Config
|
||||
}
|
||||
|
||||
cases := map[string]testCase{
|
||||
"secure": {
|
||||
tlsConfig: tlsutil.Config{
|
||||
VerifyOutgoing: true,
|
||||
VerifyServerHostname: true,
|
||||
TLSMinVersion: "tls12",
|
||||
PreferServerCipherSuites: true,
|
||||
CAFile: cafile,
|
||||
CipherSuites: parseCiphers(t, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"),
|
||||
},
|
||||
expected: config.Config{
|
||||
TLS: &config.TLS{
|
||||
VerifyOutgoing: true,
|
||||
VerifyServerHostname: true,
|
||||
MinVersion: "tls12",
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
},
|
||||
},
|
||||
},
|
||||
"less-secure": {
|
||||
tlsConfig: tlsutil.Config{
|
||||
VerifyOutgoing: true,
|
||||
VerifyServerHostname: false,
|
||||
TLSMinVersion: "tls10",
|
||||
PreferServerCipherSuites: false,
|
||||
CAFile: cafile,
|
||||
CipherSuites: parseCiphers(t, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"),
|
||||
},
|
||||
expected: config.Config{
|
||||
TLS: &config.TLS{
|
||||
VerifyOutgoing: true,
|
||||
VerifyServerHostname: false,
|
||||
MinVersion: "tls10",
|
||||
PreferServerCipherSuites: false,
|
||||
CipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tcase := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
logger := testutil.Logger(t)
|
||||
configurator, err := tlsutil.NewConfigurator(tcase.tlsConfig, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
cluster := &Cluster{
|
||||
srv: &Server{
|
||||
tlsConfigurator: configurator,
|
||||
},
|
||||
}
|
||||
|
||||
var actual config.Config
|
||||
err = cluster.updateTLSSettingsInConfig(AutoConfigOptions{}, &actual)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tcase.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoConfig_updateGossipEncryptionInConfig(t *testing.T) {
|
||||
type testCase struct {
|
||||
conf memberlist.Config
|
||||
expected config.Config
|
||||
}
|
||||
|
||||
gossipKey := make([]byte, 32)
|
||||
// this is not cryptographic randomness and is not secure but for the sake of this test its all we need.
|
||||
n, err := rand.Read(gossipKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 32, n)
|
||||
gossipKeyEncoded := base64.StdEncoding.EncodeToString(gossipKey)
|
||||
|
||||
keyring, err := memberlist.NewKeyring(nil, gossipKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := map[string]testCase{
|
||||
"encryption-required": {
|
||||
conf: memberlist.Config{
|
||||
Keyring: keyring,
|
||||
GossipVerifyIncoming: true,
|
||||
GossipVerifyOutgoing: true,
|
||||
},
|
||||
expected: config.Config{
|
||||
Gossip: &config.Gossip{
|
||||
Encryption: &config.GossipEncryption{
|
||||
Key: gossipKeyEncoded,
|
||||
VerifyIncoming: true,
|
||||
VerifyOutgoing: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"encryption-allowed": {
|
||||
conf: memberlist.Config{
|
||||
Keyring: keyring,
|
||||
GossipVerifyIncoming: false,
|
||||
GossipVerifyOutgoing: false,
|
||||
},
|
||||
expected: config.Config{
|
||||
Gossip: &config.Gossip{
|
||||
Encryption: &config.GossipEncryption{
|
||||
Key: gossipKeyEncoded,
|
||||
VerifyIncoming: false,
|
||||
VerifyOutgoing: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"encryption-disabled": {
|
||||
// zero values all around - if no keyring is configured then the gossip
|
||||
// encryption settings should not be set.
|
||||
},
|
||||
}
|
||||
|
||||
for name, tcase := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cluster := Cluster{
|
||||
srv: &Server{
|
||||
config: DefaultConfig(),
|
||||
},
|
||||
}
|
||||
|
||||
cluster.srv.config.SerfLANConfig.MemberlistConfig = &tcase.conf
|
||||
|
||||
var actual config.Config
|
||||
err := cluster.updateGossipEncryptionInConfig(AutoConfigOptions{}, &actual)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tcase.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoConfig_updateTLSCertificatesInConfig(t *testing.T) {
|
||||
type testCase struct {
|
||||
serverConfig Config
|
||||
expected config.Config
|
||||
}
|
||||
|
||||
cases := map[string]testCase{
|
||||
"auto_encrypt-enabled": {
|
||||
serverConfig: Config{
|
||||
ConnectEnabled: true,
|
||||
AutoEncryptAllowTLS: true,
|
||||
},
|
||||
expected: config.Config{
|
||||
AutoEncrypt: &config.AutoEncrypt{TLS: true},
|
||||
},
|
||||
},
|
||||
"auto_encrypt-disabled": {
|
||||
serverConfig: Config{
|
||||
ConnectEnabled: true,
|
||||
AutoEncryptAllowTLS: false,
|
||||
},
|
||||
expected: config.Config{
|
||||
AutoEncrypt: &config.AutoEncrypt{TLS: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tcase := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cluster := Cluster{
|
||||
srv: &Server{
|
||||
config: &tcase.serverConfig,
|
||||
},
|
||||
}
|
||||
|
||||
var actual config.Config
|
||||
err := cluster.updateTLSCertificatesInConfig(AutoConfigOptions{}, &actual)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tcase.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoConfig_updateACLsInConfig(t *testing.T) {
|
||||
type testCase struct {
|
||||
patch func(c *Config)
|
||||
expected config.Config
|
||||
verify func(t *testing.T, c *config.Config)
|
||||
err string
|
||||
}
|
||||
|
||||
cases := map[string]testCase{
|
||||
"enabled": {
|
||||
patch: func(c *Config) {
|
||||
c.ACLsEnabled = true
|
||||
c.ACLPolicyTTL = 7 * time.Second
|
||||
c.ACLRoleTTL = 10 * time.Second
|
||||
c.ACLTokenTTL = 12 * time.Second
|
||||
c.ACLDisabledTTL = 31 * time.Second
|
||||
c.ACLDefaultPolicy = "allow"
|
||||
c.ACLDownPolicy = "deny"
|
||||
c.ACLEnableKeyListPolicy = true
|
||||
},
|
||||
expected: config.Config{
|
||||
ACL: &config.ACL{
|
||||
Enabled: true,
|
||||
PolicyTTL: "7s",
|
||||
RoleTTL: "10s",
|
||||
TokenTTL: "12s",
|
||||
DisabledTTL: "31s",
|
||||
DownPolicy: "deny",
|
||||
DefaultPolicy: "allow",
|
||||
EnableKeyListPolicy: true,
|
||||
Tokens: &config.ACLTokens{Agent: "verified"},
|
||||
},
|
||||
},
|
||||
verify: func(t *testing.T, c *config.Config) {
|
||||
t.Helper()
|
||||
// the agent token secret is non-deterministically generated
|
||||
// So we want to validate that one was set and overwrite with
|
||||
// a value that the expected configurate wants.
|
||||
require.NotNil(t, c)
|
||||
require.NotNil(t, c.ACL)
|
||||
require.NotNil(t, c.ACL.Tokens)
|
||||
require.NotEmpty(t, c.ACL.Tokens.Agent)
|
||||
c.ACL.Tokens.Agent = "verified"
|
||||
},
|
||||
},
|
||||
"disabled": {
|
||||
patch: func(c *Config) {
|
||||
c.ACLsEnabled = false
|
||||
c.ACLPolicyTTL = 7 * time.Second
|
||||
c.ACLRoleTTL = 10 * time.Second
|
||||
c.ACLTokenTTL = 12 * time.Second
|
||||
c.ACLDisabledTTL = 31 * time.Second
|
||||
c.ACLDefaultPolicy = "allow"
|
||||
c.ACLDownPolicy = "deny"
|
||||
c.ACLEnableKeyListPolicy = true
|
||||
},
|
||||
expected: config.Config{
|
||||
ACL: &config.ACL{
|
||||
Enabled: false,
|
||||
PolicyTTL: "7s",
|
||||
RoleTTL: "10s",
|
||||
TokenTTL: "12s",
|
||||
DisabledTTL: "31s",
|
||||
DownPolicy: "deny",
|
||||
DefaultPolicy: "allow",
|
||||
EnableKeyListPolicy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"local-tokens-disabled": {
|
||||
patch: func(c *Config) {
|
||||
c.PrimaryDatacenter = "somewhere else"
|
||||
},
|
||||
err: "Agent Auto Configuration requires local token usage to be enabled in this datacenter",
|
||||
},
|
||||
}
|
||||
for name, tcase := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, s, _ := testACLServerWithConfig(t, tcase.patch, false)
|
||||
|
||||
waitForLeaderEstablishment(t, s)
|
||||
|
||||
cluster := Cluster{srv: s}
|
||||
|
||||
var actual config.Config
|
||||
err := cluster.updateACLsInConfig(AutoConfigOptions{NodeName: "something"}, &actual)
|
||||
if tcase.err != "" {
|
||||
testutil.RequireErrorContains(t, err, tcase.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
if tcase.verify != nil {
|
||||
tcase.verify(t, &actual)
|
||||
}
|
||||
require.Equal(t, tcase.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoConfig_updateJoinAddressesInConfig(t *testing.T) {
|
||||
conf := testClusterConfig{
|
||||
Datacenter: "primary",
|
||||
Servers: 3,
|
||||
}
|
||||
|
||||
nodes := newTestCluster(t, &conf)
|
||||
|
||||
cluster := Cluster{srv: nodes.Servers[0]}
|
||||
|
||||
var actual config.Config
|
||||
err := cluster.updateJoinAddressesInConfig(AutoConfigOptions{}, &actual)
|
||||
require.NoError(t, err)
|
||||
|
||||
var expected []string
|
||||
for _, srv := range nodes.Servers {
|
||||
expected = append(expected, fmt.Sprintf("127.0.0.1:%d", srv.config.SerfLANConfig.MemberlistConfig.BindPort))
|
||||
}
|
||||
require.NotNil(t, actual.Gossip)
|
||||
require.ElementsMatch(t, expected, actual.Gossip.RetryJoinLAN)
|
||||
}
|
|
@ -305,6 +305,17 @@ type Config struct {
|
|||
// by default in Consul 1.0 and later.
|
||||
ACLEnableKeyListPolicy bool
|
||||
|
||||
AutoConfigEnabled bool
|
||||
AutoConfigIntroToken string
|
||||
AutoConfigIntroTokenFile string
|
||||
AutoConfigServerAddresses []string
|
||||
AutoConfigDNSSANs []string
|
||||
AutoConfigIPSANs []net.IP
|
||||
AutoConfigAuthzEnabled bool
|
||||
AutoConfigAuthzAuthMethod structs.ACLAuthMethod
|
||||
AutoConfigAuthzClaimAssertions []string
|
||||
AutoConfigAuthzAllowReuse bool
|
||||
|
||||
// TombstoneTTL is used to control how long KV tombstones are retained.
|
||||
// This provides a window of time where the X-Consul-Index is monotonic.
|
||||
// Outside this window, the index may not be monotonic. This is a result
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/hashicorp/consul/acl"
|
||||
ca "github.com/hashicorp/consul/agent/connect/ca"
|
||||
"github.com/hashicorp/consul/agent/consul/authmethod"
|
||||
"github.com/hashicorp/consul/agent/consul/authmethod/ssoauth"
|
||||
"github.com/hashicorp/consul/agent/consul/autopilot"
|
||||
"github.com/hashicorp/consul/agent/consul/fsm"
|
||||
"github.com/hashicorp/consul/agent/consul/state"
|
||||
|
@ -834,6 +835,27 @@ func (s *Server) setupRPC() error {
|
|||
// been configured.
|
||||
s.insecureRPCServer.Register(&AutoEncrypt{srv: s})
|
||||
|
||||
// Setup the AutoConfig JWT Authorizer
|
||||
var authz AutoConfigAuthorizer
|
||||
if s.config.AutoConfigAuthzEnabled {
|
||||
// create the auto config authorizer from the JWT authmethod
|
||||
validator, err := ssoauth.NewValidator(s.logger, &s.config.AutoConfigAuthzAuthMethod)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to initialize JWT Auto Config Authorizer: %w", err)
|
||||
}
|
||||
|
||||
authz = &jwtAuthorizer{
|
||||
validator: validator,
|
||||
allowReuse: s.config.AutoConfigAuthzAllowReuse,
|
||||
claimAssertions: s.config.AutoConfigAuthzClaimAssertions,
|
||||
}
|
||||
} else {
|
||||
// This authorizer always returns that the endpoint is disabled
|
||||
authz = &disabledAuthorizer{}
|
||||
}
|
||||
// now register with the insecure RPC server
|
||||
s.insecureRPCServer.Register(&Cluster{srv: s, authorizer: authz})
|
||||
|
||||
ln, err := net.ListenTCP("tcp", s.config.RPCAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -2,6 +2,10 @@ package consul
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/rpc"
|
||||
|
@ -38,6 +42,46 @@ const (
|
|||
TestDefaultMasterToken = "d9f05e83-a7ae-47ce-839e-c0d53a68c00a"
|
||||
)
|
||||
|
||||
// testTLSCertificates Generates a TLS CA and server key/cert and returns them
|
||||
// in PEM encoded form.
|
||||
func testTLSCertificates(serverName string) (cert string, key string, cacert string, err error) {
|
||||
// generate CA
|
||||
serial, err := tlsutil.GenerateSerialNumber()
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
ca, err := tlsutil.GenerateCA(signer, serial, 365, nil)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
// generate leaf
|
||||
serial, err = tlsutil.GenerateSerialNumber()
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
cert, privateKey, err := tlsutil.GenerateCert(
|
||||
signer,
|
||||
ca,
|
||||
serial,
|
||||
"Test Cert Name",
|
||||
365,
|
||||
[]string{serverName},
|
||||
nil,
|
||||
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
return cert, privateKey, ca, nil
|
||||
}
|
||||
|
||||
// testServerACLConfig wraps another arbitrary Config altering callback
|
||||
// to setup some common ACL configurations. A new callback func will
|
||||
// be returned that has the original callback invoked after setting
|
||||
|
|
|
@ -80,17 +80,15 @@ function main {
|
|||
local proto_go_path=${proto_path%%.proto}.pb.go
|
||||
local proto_go_bin_path=${proto_path%%.proto}.pb.binary.go
|
||||
|
||||
local go_proto_out=""
|
||||
local sep=""
|
||||
local go_proto_out="paths=source_relative"
|
||||
if is_set "${grpc}"
|
||||
then
|
||||
go_proto_out="plugins=grpc"
|
||||
sep=","
|
||||
go_proto_out="${go_proto_out},plugins=grpc"
|
||||
fi
|
||||
|
||||
if is_set "${imp_replace}"
|
||||
then
|
||||
go_proto_out="${go_proto_out}${sep}${gogo_proto_imp_replace}"
|
||||
go_proto_out="${go_proto_out},${gogo_proto_imp_replace}"
|
||||
fi
|
||||
|
||||
if test -n "${go_proto_out}"
|
||||
|
@ -98,15 +96,19 @@ function main {
|
|||
go_proto_out="${go_proto_out}:"
|
||||
fi
|
||||
|
||||
# How we run protoc probably needs some documentation.
|
||||
#
|
||||
# This is the path to where
|
||||
# -I="${gogo_proto_path}/protobuf" \
|
||||
local -i ret=0
|
||||
status_stage "Generating ${proto_path} into ${proto_go_path} and ${proto_go_bin_path}"
|
||||
debug_run protoc \
|
||||
-I="$(dirname ${proto_path})" \
|
||||
-I="${gogo_proto_path}/protobuf" \
|
||||
-I="${gogo_proto_path}" \
|
||||
-I="${gogo_proto_mod_path}" \
|
||||
--gofast_out="${go_proto_out}$(dirname ${proto_path})" \
|
||||
--go-binary_out="$(dirname ${proto_path})" \
|
||||
-I="${SOURCE_DIR}" \
|
||||
--gofast_out="${go_proto_out}${SOURCE_DIR}" \
|
||||
--go-binary_out="${SOURCE_DIR}" \
|
||||
"${proto_path}"
|
||||
if test $? -ne 0
|
||||
then
|
||||
|
|
|
@ -490,13 +490,13 @@ func init() {
|
|||
// test. These are cached between runs but do not persist between restarts
|
||||
// of the test binary.
|
||||
var err error
|
||||
ecdsaPublicKey, ecdsaPrivateKey, err = generateKey()
|
||||
ecdsaPublicKey, ecdsaPrivateKey, err = GenerateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateKey() (pub, priv string, err error) {
|
||||
func GenerateKey() (pub, priv string, err error) {
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("error generating private key: %v", err)
|
||||
|
|
|
@ -936,6 +936,7 @@ func ParseCiphers(cipherStr string) ([]uint16, error) {
|
|||
}
|
||||
ciphers := strings.Split(cipherStr, ",")
|
||||
|
||||
// Note: this needs to be kept up to date with the cipherMap in CipherString
|
||||
cipherMap := map[string]uint16{
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
|
@ -958,3 +959,31 @@ func ParseCiphers(cipherStr string) ([]uint16, error) {
|
|||
|
||||
return suites, nil
|
||||
}
|
||||
|
||||
// CipherString performs the inverse operation of ParseCiphers
|
||||
func CipherString(ciphers []uint16) (string, error) {
|
||||
// Note: this needs to be kept up to date with the cipherMap in ParseCiphers
|
||||
cipherMap := map[uint16]string{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
}
|
||||
|
||||
cipherStrings := make([]string, len(ciphers))
|
||||
for i, cipher := range ciphers {
|
||||
if v, ok := cipherMap[cipher]; ok {
|
||||
cipherStrings[i] = v
|
||||
} else {
|
||||
return "", fmt.Errorf("unsupported cipher %d", cipher)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(cipherStrings, ","), nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue