Merge pull request #8035 from hashicorp/feature/auto-config/server-rpc
This commit is contained in:
commit
8c601ad8db
|
@ -2,6 +2,7 @@ package acl
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -70,3 +71,8 @@ func (e PermissionDeniedError) Error() string {
|
|||
}
|
||||
return errPermissionDenied
|
||||
}
|
||||
|
||||
func PermissionDenied(msg string, args ...interface{}) PermissionDeniedError {
|
||||
cause := fmt.Sprintf(msg, args...)
|
||||
return PermissionDeniedError{Cause: cause}
|
||||
}
|
||||
|
|
|
@ -1402,6 +1402,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