Support Connect CAs that can't cross sign (#6726)
* Support Connect CAs that can't cross sign * revert spurios mod changes from make tools * Add log warning when forcing CA rotation * Fixup SupportsCrossSigning to report errors and work with Plugin interface (fixes tests) * Fix failing snake_case test * Remove misleading comment * Revert "Remove misleading comment" This reverts commit bc4db9cabed8ad5d0e39b30e1fe79196d248349c. * Remove misleading comment * Regen proto files messed up by rebase
This commit is contained in:
parent
ca96d5fa72
commit
1197b43c7b
|
@ -238,3 +238,24 @@ func (_m *MockProvider) State() (map[string]string, error) {
|
|||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SupportsCrossSigning provides a mock function with given fields:
|
||||
func (_m *MockProvider) SupportsCrossSigning() (bool, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
|
|
@ -137,6 +137,16 @@ func (msg *CrossSignCAResponse) UnmarshalBinary(b []byte) error {
|
|||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *BoolResponse) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler
|
||||
func (msg *BoolResponse) UnmarshalBinary(b []byte) error {
|
||||
return proto.Unmarshal(b, msg)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler
|
||||
func (msg *Empty) MarshalBinary() ([]byte, error) {
|
||||
return proto.Marshal(msg)
|
||||
|
|
|
@ -666,6 +666,53 @@ func (m *CrossSignCAResponse) GetCrtPem() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
type BoolResponse struct {
|
||||
Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *BoolResponse) Reset() { *m = BoolResponse{} }
|
||||
func (m *BoolResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*BoolResponse) ProtoMessage() {}
|
||||
func (*BoolResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c6a9f3c02af3d1c8, []int{13}
|
||||
}
|
||||
func (m *BoolResponse) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *BoolResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_BoolResponse.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 *BoolResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_BoolResponse.Merge(m, src)
|
||||
}
|
||||
func (m *BoolResponse) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *BoolResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_BoolResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_BoolResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *BoolResponse) GetOk() bool {
|
||||
if m != nil {
|
||||
return m.Ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Protobufs doesn't allow no req/resp so in the cases where there are
|
||||
// no arguments we use the Empty message.
|
||||
type Empty struct {
|
||||
|
@ -678,7 +725,7 @@ func (m *Empty) Reset() { *m = Empty{} }
|
|||
func (m *Empty) String() string { return proto.CompactTextString(m) }
|
||||
func (*Empty) ProtoMessage() {}
|
||||
func (*Empty) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_c6a9f3c02af3d1c8, []int{13}
|
||||
return fileDescriptor_c6a9f3c02af3d1c8, []int{14}
|
||||
}
|
||||
func (m *Empty) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -721,48 +768,52 @@ func init() {
|
|||
proto.RegisterType((*SignResponse)(nil), "plugin.SignResponse")
|
||||
proto.RegisterType((*SignIntermediateResponse)(nil), "plugin.SignIntermediateResponse")
|
||||
proto.RegisterType((*CrossSignCAResponse)(nil), "plugin.CrossSignCAResponse")
|
||||
proto.RegisterType((*BoolResponse)(nil), "plugin.BoolResponse")
|
||||
proto.RegisterType((*Empty)(nil), "plugin.Empty")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("provider.proto", fileDescriptor_c6a9f3c02af3d1c8) }
|
||||
|
||||
var fileDescriptor_c6a9f3c02af3d1c8 = []byte{
|
||||
// 560 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x55, 0xc1, 0x6e, 0xd3, 0x40,
|
||||
0x10, 0xc5, 0x69, 0x93, 0x34, 0xd3, 0x94, 0x5a, 0xdb, 0xd0, 0x18, 0x03, 0x4e, 0x64, 0x01, 0x09,
|
||||
0x82, 0x46, 0x82, 0x82, 0x2a, 0x71, 0x22, 0x58, 0x50, 0x55, 0x5c, 0x8a, 0x23, 0xae, 0x44, 0xc1,
|
||||
0x59, 0xa2, 0x95, 0x62, 0xaf, 0xd9, 0x5d, 0x57, 0xf0, 0x27, 0x7c, 0x12, 0x47, 0x3e, 0x01, 0x85,
|
||||
0x7f, 0xe0, 0x8c, 0xbc, 0xb1, 0x1d, 0x7b, 0xe3, 0xd6, 0xb7, 0xcc, 0xf8, 0xcd, 0xdb, 0x79, 0xb3,
|
||||
0x6f, 0x36, 0x70, 0x3b, 0x64, 0xf4, 0x8a, 0xcc, 0x31, 0x1b, 0x85, 0x8c, 0x0a, 0x8a, 0x1a, 0xe1,
|
||||
0x32, 0x5a, 0x90, 0xc0, 0xfe, 0x0e, 0xba, 0x43, 0x83, 0xaf, 0x64, 0x11, 0x31, 0xec, 0xe2, 0x6f,
|
||||
0x11, 0xe6, 0x02, 0x3d, 0x00, 0xf0, 0x96, 0x11, 0x17, 0x98, 0x4d, 0xc9, 0xdc, 0xd0, 0xfa, 0xda,
|
||||
0xb0, 0xe5, 0xb6, 0x92, 0xcc, 0xc5, 0x1c, 0x75, 0xa1, 0x49, 0xf8, 0x94, 0x51, 0x2a, 0x8c, 0x5a,
|
||||
0x5f, 0x1b, 0xee, 0xb9, 0x0d, 0xc2, 0x5d, 0x4a, 0x05, 0x3a, 0x86, 0x86, 0x27, 0xb9, 0x8c, 0x9d,
|
||||
0xbe, 0x36, 0x6c, 0xbb, 0x49, 0x84, 0x3a, 0x50, 0xe7, 0x62, 0x26, 0xb0, 0xb1, 0x2b, 0xd3, 0xeb,
|
||||
0xc0, 0xfe, 0x0c, 0xc7, 0x13, 0x2c, 0x2e, 0x02, 0x81, 0x99, 0x8f, 0xe7, 0x64, 0x26, 0xb2, 0xf3,
|
||||
0x9f, 0x80, 0x4e, 0x72, 0xe9, 0x69, 0x88, 0xfd, 0xa4, 0x8b, 0xc3, 0x7c, 0xfe, 0x12, 0xfb, 0xe8,
|
||||
0x2e, 0xec, 0xc5, 0x8d, 0x48, 0x48, 0x4d, 0x42, 0x9a, 0x71, 0x7c, 0x89, 0x7d, 0xbb, 0x07, 0xfb,
|
||||
0x13, 0xb2, 0x08, 0x52, 0x52, 0x1d, 0x76, 0x3c, 0xce, 0x24, 0x4f, 0xdb, 0x8d, 0x7f, 0xda, 0x4f,
|
||||
0xa1, 0x1b, 0x03, 0xca, 0x3a, 0xd8, 0x06, 0x3f, 0x06, 0xe4, 0x30, 0xca, 0x79, 0x5c, 0xe1, 0x8c,
|
||||
0xf3, 0x38, 0x26, 0x32, 0x1c, 0x13, 0xf6, 0x23, 0x38, 0x98, 0x08, 0xc9, 0xc4, 0x43, 0x1a, 0x70,
|
||||
0xbc, 0x11, 0xaf, 0xe5, 0xc5, 0x9f, 0x00, 0x1a, 0x7b, 0x82, 0x5c, 0xe1, 0x78, 0x70, 0x19, 0xb6,
|
||||
0x0b, 0x4d, 0x8f, 0x89, 0x9c, 0xde, 0x86, 0xc7, 0xa4, 0x96, 0xd7, 0xd0, 0x3b, 0xc7, 0x01, 0x66,
|
||||
0x33, 0x81, 0xf3, 0xed, 0x3a, 0x13, 0xb7, 0x50, 0xcb, 0x59, 0xa1, 0x96, 0xb3, 0xb8, 0xf6, 0x15,
|
||||
0x98, 0xeb, 0xa3, 0x8a, 0x42, 0xab, 0x8e, 0x3c, 0x83, 0xfb, 0x65, 0x47, 0x56, 0x17, 0x0e, 0xa0,
|
||||
0xbd, 0x9e, 0x7b, 0x15, 0xf0, 0x14, 0x8c, 0xed, 0xf9, 0x57, 0x15, 0x8d, 0xe0, 0xa8, 0x70, 0x0f,
|
||||
0x55, 0xf8, 0x26, 0xd4, 0xdf, 0xf9, 0xa1, 0xf8, 0xf1, 0xe2, 0x5f, 0x1d, 0x6a, 0xce, 0x18, 0xbd,
|
||||
0x84, 0x56, 0xe6, 0x77, 0x64, 0x8c, 0xd6, 0x5b, 0x30, 0x52, 0x57, 0xc0, 0x3c, 0x48, 0xbf, 0xc8,
|
||||
0x62, 0x74, 0x02, 0x75, 0x79, 0xab, 0xa8, 0x98, 0x37, 0xef, 0xa4, 0x61, 0xf1, 0xce, 0x9f, 0x41,
|
||||
0x3b, 0x9d, 0x9d, 0x5c, 0x0c, 0xa5, 0x4a, 0x21, 0x3f, 0x03, 0xd8, 0x78, 0x41, 0xc5, 0x9a, 0x69,
|
||||
0x58, 0x62, 0x97, 0x8f, 0xd0, 0xbd, 0xc6, 0x15, 0x2a, 0xcb, 0x20, 0x0d, 0xab, 0x5c, 0xf4, 0x06,
|
||||
0x0e, 0x95, 0xa5, 0x44, 0x56, 0xa6, 0xb1, 0x74, 0x5b, 0x55, 0x35, 0xe7, 0xa9, 0xb3, 0x0b, 0x24,
|
||||
0x4a, 0x3f, 0x76, 0x51, 0x55, 0xa9, 0x05, 0x3e, 0x40, 0xa7, 0xac, 0x5b, 0x95, 0xea, 0xe1, 0x4d,
|
||||
0xd2, 0x32, 0xb2, 0xe7, 0xb0, 0x1b, 0x3b, 0x06, 0x1d, 0x65, 0x62, 0x36, 0x4f, 0x83, 0xd9, 0x29,
|
||||
0x26, 0x93, 0x92, 0x4f, 0xa0, 0xab, 0xf6, 0x44, 0xbd, 0x3c, 0xb2, 0x6c, 0x18, 0xfd, 0xeb, 0x01,
|
||||
0x09, 0xed, 0x7b, 0xd8, 0xcf, 0x19, 0x18, 0x65, 0xf7, 0xbb, 0xfd, 0xba, 0x98, 0xf7, 0x4a, 0xbf,
|
||||
0x25, 0x3c, 0x03, 0x68, 0x3a, 0x4b, 0x3c, 0x0b, 0xa2, 0xf0, 0x66, 0x7b, 0xbd, 0xd5, 0x7f, 0xad,
|
||||
0x2c, 0xed, 0xf7, 0xca, 0xd2, 0xfe, 0xac, 0x2c, 0xed, 0xe7, 0x5f, 0xeb, 0xd6, 0x97, 0x86, 0xfc,
|
||||
0x0b, 0x38, 0xfd, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xd1, 0xcf, 0x1a, 0x9a, 0x14, 0x06, 0x00, 0x00,
|
||||
// 599 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x55, 0xd1, 0x6e, 0xd3, 0x4a,
|
||||
0x10, 0xbd, 0x4e, 0x5b, 0x27, 0x99, 0xa6, 0x6d, 0xb4, 0xcd, 0x6d, 0x8c, 0x01, 0x27, 0xb2, 0x80,
|
||||
0x04, 0x41, 0x23, 0x41, 0x41, 0x95, 0xe0, 0x85, 0xd4, 0x82, 0xaa, 0xe2, 0xa5, 0x38, 0xe2, 0x95,
|
||||
0x28, 0x38, 0x4b, 0x64, 0x35, 0xf1, 0x9a, 0xdd, 0x75, 0x05, 0x7f, 0xc2, 0x7f, 0xf0, 0x13, 0x3c,
|
||||
0xf2, 0x09, 0x28, 0xfc, 0x08, 0xf2, 0xc6, 0xde, 0xd8, 0x1b, 0xb7, 0x7e, 0xcb, 0xcc, 0x9e, 0x39,
|
||||
0x3b, 0x67, 0xf6, 0x8c, 0x03, 0xfb, 0x21, 0x25, 0xd7, 0xfe, 0x14, 0xd3, 0x41, 0x48, 0x09, 0x27,
|
||||
0x48, 0x0f, 0xe7, 0xd1, 0xcc, 0x0f, 0xec, 0x6f, 0xd0, 0x74, 0x48, 0xf0, 0xc5, 0x9f, 0x45, 0x14,
|
||||
0xbb, 0xf8, 0x6b, 0x84, 0x19, 0x47, 0xf7, 0x01, 0xbc, 0x79, 0xc4, 0x38, 0xa6, 0x63, 0x7f, 0x6a,
|
||||
0x68, 0x5d, 0xad, 0x5f, 0x77, 0xeb, 0x49, 0xe6, 0x62, 0x8a, 0xda, 0x50, 0xf5, 0xd9, 0x98, 0x12,
|
||||
0xc2, 0x8d, 0x4a, 0x57, 0xeb, 0xd7, 0x5c, 0xdd, 0x67, 0x2e, 0x21, 0x1c, 0x1d, 0x81, 0xee, 0x09,
|
||||
0x2e, 0x63, 0xab, 0xab, 0xf5, 0x1b, 0x6e, 0x12, 0xa1, 0x16, 0xec, 0x30, 0x3e, 0xe1, 0xd8, 0xd8,
|
||||
0x16, 0xe9, 0x55, 0x60, 0x7f, 0x82, 0xa3, 0x11, 0xe6, 0x17, 0x01, 0xc7, 0x74, 0x81, 0xa7, 0xfe,
|
||||
0x84, 0xcb, 0xfb, 0x1f, 0x43, 0xd3, 0xcf, 0xa4, 0xc7, 0x21, 0x5e, 0x24, 0x5d, 0x1c, 0x64, 0xf3,
|
||||
0x97, 0x78, 0x81, 0xee, 0x40, 0x2d, 0x6e, 0x44, 0x40, 0x2a, 0x02, 0x52, 0x8d, 0xe3, 0x4b, 0xbc,
|
||||
0xb0, 0x3b, 0xb0, 0x3b, 0xf2, 0x67, 0x41, 0x4a, 0xda, 0x84, 0x2d, 0x8f, 0x51, 0xc1, 0xd3, 0x70,
|
||||
0xe3, 0x9f, 0xf6, 0x13, 0x68, 0xc7, 0x80, 0xa2, 0x0e, 0x36, 0xc1, 0x8f, 0x00, 0x39, 0x94, 0x30,
|
||||
0x16, 0x57, 0x38, 0xc3, 0x2c, 0x8e, 0x72, 0x89, 0xa3, 0xdc, 0x7e, 0x08, 0x7b, 0x23, 0x2e, 0x98,
|
||||
0x58, 0x48, 0x02, 0x86, 0xd7, 0xe2, 0xb5, 0xac, 0xf8, 0x63, 0x40, 0x43, 0x8f, 0xfb, 0xd7, 0x38,
|
||||
0x1e, 0x9c, 0xc4, 0xb6, 0xa1, 0xea, 0x51, 0x9e, 0xd1, 0xab, 0x7b, 0x54, 0x68, 0x79, 0x05, 0x9d,
|
||||
0x73, 0x1c, 0x60, 0x3a, 0xe1, 0x38, 0xdb, 0xae, 0x33, 0x72, 0x73, 0xb5, 0x8c, 0xe6, 0x6a, 0x19,
|
||||
0x8d, 0x6b, 0x5f, 0x82, 0xb9, 0xba, 0x2a, 0x2f, 0xb4, 0xec, 0xca, 0x53, 0xb8, 0x57, 0x74, 0x65,
|
||||
0x79, 0x61, 0x0f, 0x1a, 0xab, 0xb9, 0x97, 0x01, 0x4f, 0xc0, 0xd8, 0x9c, 0x7f, 0x59, 0xd1, 0x00,
|
||||
0x0e, 0x73, 0xef, 0x50, 0x86, 0xb7, 0xa0, 0x71, 0x46, 0xc8, 0x5c, 0x02, 0xf7, 0xa1, 0x42, 0xae,
|
||||
0x04, 0xa6, 0xe6, 0x56, 0xc8, 0x95, 0x5d, 0x85, 0x9d, 0xb7, 0x8b, 0x90, 0x7f, 0x7f, 0xfe, 0x53,
|
||||
0x87, 0x8a, 0x33, 0x44, 0x2f, 0xa0, 0x2e, 0xf7, 0x01, 0x19, 0x83, 0xd5, 0x96, 0x0c, 0xd4, 0x15,
|
||||
0x31, 0xf7, 0xd2, 0x13, 0x51, 0x8c, 0x8e, 0x61, 0x47, 0xbc, 0x3a, 0xca, 0xe7, 0xcd, 0xff, 0xd3,
|
||||
0x30, 0xef, 0x89, 0xa7, 0xd0, 0x48, 0x67, 0x2b, 0x16, 0x47, 0xa9, 0x52, 0xc8, 0x4f, 0x01, 0xd6,
|
||||
0x5e, 0x51, 0xb1, 0x66, 0x1a, 0x16, 0xd8, 0xe9, 0x03, 0xb4, 0x6f, 0x70, 0x8d, 0xca, 0xd2, 0x4b,
|
||||
0xc3, 0x32, 0x97, 0xbd, 0x81, 0x03, 0x65, 0x69, 0x91, 0x25, 0x35, 0x16, 0x6e, 0xb3, 0xaa, 0xe6,
|
||||
0x3c, 0x75, 0x7e, 0x8e, 0x44, 0xe9, 0xc7, 0xce, 0xab, 0x2a, 0xb4, 0xc8, 0x7b, 0x68, 0x15, 0x75,
|
||||
0xab, 0x52, 0x3d, 0xb8, 0x4d, 0x9a, 0x24, 0x7b, 0x06, 0xdb, 0xb1, 0xa3, 0xd0, 0xa1, 0x14, 0xb3,
|
||||
0xfe, 0x74, 0x98, 0xad, 0x7c, 0x32, 0x29, 0xf9, 0x08, 0x4d, 0xd5, 0xbe, 0xa8, 0x93, 0x45, 0x16,
|
||||
0x0d, 0xa3, 0x7b, 0x33, 0x20, 0xa1, 0x7d, 0x07, 0xbb, 0x19, 0x83, 0x23, 0xf9, 0xbe, 0x9b, 0x5f,
|
||||
0x1f, 0xf3, 0x6e, 0xe1, 0x59, 0xc2, 0xf3, 0x1a, 0x5a, 0xa3, 0x28, 0x0c, 0x09, 0xe5, 0x4c, 0x1e,
|
||||
0xfb, 0xc1, 0x4c, 0x1d, 0x8f, 0xd4, 0x96, 0xdb, 0x92, 0x1e, 0x54, 0x9d, 0x39, 0x9e, 0x04, 0x51,
|
||||
0x78, 0xbb, 0x37, 0xcf, 0x9a, 0xbf, 0x96, 0x96, 0xf6, 0x7b, 0x69, 0x69, 0x7f, 0x96, 0x96, 0xf6,
|
||||
0xe3, 0xaf, 0xf5, 0xdf, 0x67, 0x5d, 0xfc, 0xbf, 0x9c, 0xfc, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x9e,
|
||||
0xa1, 0xdc, 0xbb, 0x71, 0x06, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
@ -788,6 +839,7 @@ type CAClient interface {
|
|||
Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
|
||||
SignIntermediate(ctx context.Context, in *SignIntermediateRequest, opts ...grpc.CallOption) (*SignIntermediateResponse, error)
|
||||
CrossSignCA(ctx context.Context, in *CrossSignCARequest, opts ...grpc.CallOption) (*CrossSignCAResponse, error)
|
||||
SupportsCrossSigning(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*BoolResponse, error)
|
||||
Cleanup(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
|
||||
}
|
||||
|
||||
|
@ -898,6 +950,15 @@ func (c *cAClient) CrossSignCA(ctx context.Context, in *CrossSignCARequest, opts
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *cAClient) SupportsCrossSigning(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*BoolResponse, error) {
|
||||
out := new(BoolResponse)
|
||||
err := c.cc.Invoke(ctx, "/plugin.CA/SupportsCrossSigning", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *cAClient) Cleanup(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, "/plugin.CA/Cleanup", in, out, opts...)
|
||||
|
@ -920,6 +981,7 @@ type CAServer interface {
|
|||
Sign(context.Context, *SignRequest) (*SignResponse, error)
|
||||
SignIntermediate(context.Context, *SignIntermediateRequest) (*SignIntermediateResponse, error)
|
||||
CrossSignCA(context.Context, *CrossSignCARequest) (*CrossSignCAResponse, error)
|
||||
SupportsCrossSigning(context.Context, *Empty) (*BoolResponse, error)
|
||||
Cleanup(context.Context, *Empty) (*Empty, error)
|
||||
}
|
||||
|
||||
|
@ -1125,6 +1187,24 @@ func _CA_CrossSignCA_Handler(srv interface{}, ctx context.Context, dec func(inte
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CA_SupportsCrossSigning_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CAServer).SupportsCrossSigning(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/plugin.CA/SupportsCrossSigning",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CAServer).SupportsCrossSigning(ctx, req.(*Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CA_Cleanup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
if err := dec(in); err != nil {
|
||||
|
@ -1191,6 +1271,10 @@ var _CA_serviceDesc = grpc.ServiceDesc{
|
|||
MethodName: "CrossSignCA",
|
||||
Handler: _CA_CrossSignCA_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SupportsCrossSigning",
|
||||
Handler: _CA_SupportsCrossSigning_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Cleanup",
|
||||
Handler: _CA_Cleanup_Handler,
|
||||
|
@ -1579,6 +1663,37 @@ func (m *CrossSignCAResponse) MarshalTo(dAtA []byte) (int, error) {
|
|||
return i, nil
|
||||
}
|
||||
|
||||
func (m *BoolResponse) 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 *BoolResponse) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.Ok {
|
||||
dAtA[i] = 0x8
|
||||
i++
|
||||
if m.Ok {
|
||||
dAtA[i] = 1
|
||||
} else {
|
||||
dAtA[i] = 0
|
||||
}
|
||||
i++
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *Empty) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
|
@ -1832,6 +1947,21 @@ func (m *CrossSignCAResponse) Size() (n int) {
|
|||
return n
|
||||
}
|
||||
|
||||
func (m *BoolResponse) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.Ok {
|
||||
n += 2
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Empty) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
|
@ -3103,6 +3233,80 @@ func (m *CrossSignCAResponse) Unmarshal(dAtA []byte) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
func (m *BoolResponse) 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 ErrIntOverflowProvider
|
||||
}
|
||||
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: BoolResponse: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: BoolResponse: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Ok", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowProvider
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Ok = bool(v != 0)
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipProvider(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthProvider
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthProvider
|
||||
}
|
||||
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 *Empty) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
|
|
|
@ -24,6 +24,7 @@ service CA {
|
|||
rpc Sign(SignRequest) returns (SignResponse);
|
||||
rpc SignIntermediate(SignIntermediateRequest) returns (SignIntermediateResponse);
|
||||
rpc CrossSignCA(CrossSignCARequest) returns (CrossSignCAResponse);
|
||||
rpc SupportsCrossSigning(Empty) returns (BoolResponse);
|
||||
rpc Cleanup(Empty) returns (Empty);
|
||||
}
|
||||
|
||||
|
@ -83,6 +84,10 @@ message CrossSignCAResponse {
|
|||
string crt_pem = 1;
|
||||
}
|
||||
|
||||
message BoolResponse {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
||||
// Protobufs doesn't allow no req/resp so in the cases where there are
|
||||
// no arguments we use the Empty message.
|
||||
message Empty {}
|
||||
|
|
|
@ -97,6 +97,11 @@ func (p *providerPluginGRPCServer) CrossSignCA(_ context.Context, req *CrossSign
|
|||
return &CrossSignCAResponse{CrtPem: crtPEM}, err
|
||||
}
|
||||
|
||||
func (p *providerPluginGRPCServer) SupportsCrossSigning(context.Context, *Empty) (*BoolResponse, error) {
|
||||
ok, err := p.impl.SupportsCrossSigning()
|
||||
return &BoolResponse{Ok: ok}, err
|
||||
}
|
||||
|
||||
func (p *providerPluginGRPCServer) Cleanup(context.Context, *Empty) (*Empty, error) {
|
||||
return &Empty{}, p.impl.Cleanup()
|
||||
}
|
||||
|
@ -230,6 +235,11 @@ func (p *providerPluginGRPCClient) CrossSignCA(crt *x509.Certificate) (string, e
|
|||
return resp.CrtPem, nil
|
||||
}
|
||||
|
||||
func (p *providerPluginGRPCClient) SupportsCrossSigning() (bool, error) {
|
||||
resp, err := p.client.SupportsCrossSigning(p.doneCtx, &Empty{})
|
||||
return resp.Ok, err
|
||||
}
|
||||
|
||||
func (p *providerPluginGRPCClient) Cleanup() error {
|
||||
_, err := p.client.Cleanup(p.doneCtx, &Empty{})
|
||||
return p.err(err)
|
||||
|
|
|
@ -192,6 +192,12 @@ func (p *providerPluginRPCClient) CrossSignCA(crt *x509.Certificate) (string, er
|
|||
return resp.CrtPem, err
|
||||
}
|
||||
|
||||
func (p *providerPluginRPCClient) SupportsCrossSigning() (bool, error) {
|
||||
var out BoolResponse
|
||||
err := p.client.Call("Plugin.SupportsCrossSigning", struct{}{}, &out)
|
||||
return out.Ok, err
|
||||
}
|
||||
|
||||
func (p *providerPluginRPCClient) Cleanup() error {
|
||||
return p.client.Call("Plugin.Cleanup", struct{}{}, &struct{}{})
|
||||
}
|
||||
|
|
|
@ -84,16 +84,26 @@ type Provider interface {
|
|||
// of 0 to ensure that the certificate cannot be used to generate further CA certs.
|
||||
SignIntermediate(*x509.CertificateRequest) (string, error)
|
||||
|
||||
// CrossSignCA must accept a CA certificate from another CA provider
|
||||
// and cross sign it exactly as it is such that it forms a chain back the the
|
||||
// CrossSignCA must accept a CA certificate from another CA provider and cross
|
||||
// sign it exactly as it is such that it forms a chain back the the
|
||||
// CAProvider's current root. Specifically, the Distinguished Name, Subject
|
||||
// Alternative Name, SubjectKeyID and other relevant extensions must be kept.
|
||||
// The resulting certificate must have a distinct Serial Number and the
|
||||
// AuthorityKeyID set to the CAProvider's current signing key as well as the
|
||||
// Issuer related fields changed as necessary. The resulting certificate is
|
||||
// returned as a PEM formatted string.
|
||||
//
|
||||
// If the CA provider does not support this operation, it may return an error
|
||||
// provided `SupportsCrossSigning` also returns false.
|
||||
CrossSignCA(*x509.Certificate) (string, error)
|
||||
|
||||
// SupportsCrossSigning should indicate whether the CA provider supports
|
||||
// cross-signing an external root to provide a seamless rotation. If the CA
|
||||
// does not support this, the user will have to force an upgrade when that CA
|
||||
// provider is the current CA as the upgrade may cause interruptions to
|
||||
// connectivity during the rollout.
|
||||
SupportsCrossSigning() (bool, error)
|
||||
|
||||
// Cleanup performs any necessary cleanup that should happen when the provider
|
||||
// is shut down permanently, such as removing a temporary PKI backend in Vault
|
||||
// created for an intermediate CA.
|
||||
|
|
|
@ -508,6 +508,10 @@ func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
|||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if c.config.DisableCrossSigning {
|
||||
return "", errors.New("cross-signing disabled")
|
||||
}
|
||||
|
||||
// Get the provider state
|
||||
idx, providerState, err := c.getState()
|
||||
if err != nil {
|
||||
|
@ -568,6 +572,11 @@ func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
|||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// SupportsCrossSigning implements Provider
|
||||
func (c *ConsulProvider) SupportsCrossSigning() (bool, error) {
|
||||
return !c.config.DisableCrossSigning, nil
|
||||
}
|
||||
|
||||
// getState returns the current provider state from the state delegate, and returns
|
||||
// ErrNotInitialized if no entry is found.
|
||||
func (c *ConsulProvider) getState() (uint64, *structs.CAConsulProviderState, error) {
|
||||
|
|
|
@ -389,6 +389,11 @@ func (v *VaultProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
|||
return xcCert, nil
|
||||
}
|
||||
|
||||
// SupportsCrossSigning implements Provider
|
||||
func (c *VaultProvider) SupportsCrossSigning() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Cleanup unmounts the configured intermediate PKI backend. It's fine to tear
|
||||
// this down and recreate it on small config changes because the intermediate
|
||||
// certs get bundled with the leaf certs, so there's no cost to the CA changing.
|
||||
|
|
|
@ -62,9 +62,9 @@ func (s *HTTPServer) ConnectCAConfigurationSet(resp http.ResponseWriter, req *ht
|
|||
s.parseDC(req, &args.Datacenter)
|
||||
s.parseToken(req, &args.Token)
|
||||
if err := decodeBody(req.Body, &args.Config); err != nil {
|
||||
resp.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(resp, "Request decode failed: %v", err)
|
||||
return nil, nil
|
||||
return nil, BadRequestError{
|
||||
Reason: fmt.Sprintf("Request decode failed: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
var reply interface{}
|
||||
|
|
|
@ -5,14 +5,12 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
ca "github.com/hashicorp/consul/agent/connect/ca"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -64,61 +62,117 @@ func TestConnectCARoots_list(t *testing.T) {
|
|||
func TestConnectCAConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := assert.New(t)
|
||||
a := NewTestAgent(t, t.Name(), "")
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
|
||||
expected := &structs.ConsulCAProviderConfig{
|
||||
RotationPeriod: 90 * 24 * time.Hour,
|
||||
}
|
||||
expected.LeafCertTTL = 72 * time.Hour
|
||||
expected.PrivateKeyType = connect.DefaultPrivateKeyType
|
||||
expected.PrivateKeyBits = connect.DefaultPrivateKeyBits
|
||||
|
||||
// Get the initial config.
|
||||
{
|
||||
req, _ := http.NewRequest("GET", "/v1/connect/ca/configuration", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.ConnectCAConfiguration(resp, req)
|
||||
assert.NoError(err)
|
||||
|
||||
value := obj.(structs.CAConfiguration)
|
||||
parsed, err := ca.ParseConsulCAConfig(value.Config)
|
||||
assert.NoError(err)
|
||||
assert.Equal("consul", value.Provider)
|
||||
assert.Equal(expected, parsed)
|
||||
}
|
||||
|
||||
// Set the config.
|
||||
{
|
||||
body := bytes.NewBuffer([]byte(`
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
wantErr bool
|
||||
wantCfg structs.CAConfiguration
|
||||
}{
|
||||
{
|
||||
"Provider": "consul",
|
||||
"Config": {
|
||||
"LeafCertTTL": "72h",
|
||||
"RotationPeriod": "1h"
|
||||
}
|
||||
}`))
|
||||
req, _ := http.NewRequest("PUT", "/v1/connect/ca/configuration", body)
|
||||
resp := httptest.NewRecorder()
|
||||
_, err := a.srv.ConnectCAConfiguration(resp, req)
|
||||
assert.NoError(err)
|
||||
name: "basic",
|
||||
body: `
|
||||
{
|
||||
"Provider": "consul",
|
||||
"Config": {
|
||||
"LeafCertTTL": "72h",
|
||||
"RotationPeriod": "1h"
|
||||
}
|
||||
}`,
|
||||
wantErr: false,
|
||||
wantCfg: structs.CAConfiguration{
|
||||
Provider: "consul",
|
||||
ClusterID: connect.TestClusterID,
|
||||
Config: map[string]interface{}{
|
||||
"LeafCertTTL": "72h",
|
||||
"RotationPeriod": "1h",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "force without cross sign CamelCase",
|
||||
body: `
|
||||
{
|
||||
"Provider": "consul",
|
||||
"Config": {
|
||||
"LeafCertTTL": "72h",
|
||||
"RotationPeriod": "1h"
|
||||
},
|
||||
"ForceWithoutCrossSigning": true
|
||||
}`,
|
||||
wantErr: false,
|
||||
wantCfg: structs.CAConfiguration{
|
||||
Provider: "consul",
|
||||
ClusterID: connect.TestClusterID,
|
||||
Config: map[string]interface{}{
|
||||
"LeafCertTTL": "72h",
|
||||
"RotationPeriod": "1h",
|
||||
},
|
||||
ForceWithoutCrossSigning: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "force without cross sign snake_case",
|
||||
// Note that config is still CamelCase. We don't currently support snake
|
||||
// case config in the API only in config files for this. Arguably that's a
|
||||
// bug but it's unrelated to the force options being tested here so we'll
|
||||
// only test the new behaviour here rather than scope creep to refactoring
|
||||
// all the CA config handling.
|
||||
body: `
|
||||
{
|
||||
"provider": "consul",
|
||||
"config": {
|
||||
"LeafCertTTL": "72h",
|
||||
"RotationPeriod": "1h"
|
||||
},
|
||||
"force_without_cross_signing": true
|
||||
}`,
|
||||
wantErr: false,
|
||||
wantCfg: structs.CAConfiguration{
|
||||
Provider: "consul",
|
||||
ClusterID: connect.TestClusterID,
|
||||
Config: map[string]interface{}{
|
||||
"LeafCertTTL": "72h",
|
||||
"RotationPeriod": "1h",
|
||||
},
|
||||
ForceWithoutCrossSigning: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// The config should be updated now.
|
||||
{
|
||||
expected.RotationPeriod = time.Hour
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
a := NewTestAgent(t, t.Name(), "")
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
|
||||
req, _ := http.NewRequest("GET", "/v1/connect/ca/configuration", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.ConnectCAConfiguration(resp, req)
|
||||
assert.NoError(err)
|
||||
// Set the config.
|
||||
{
|
||||
body := bytes.NewBuffer([]byte(tc.body))
|
||||
req, _ := http.NewRequest("PUT", "/v1/connect/ca/configuration", body)
|
||||
resp := httptest.NewRecorder()
|
||||
_, err := a.srv.ConnectCAConfiguration(resp, req)
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
value := obj.(structs.CAConfiguration)
|
||||
parsed, err := ca.ParseConsulCAConfig(value.Config)
|
||||
assert.NoError(err)
|
||||
assert.Equal("consul", value.Provider)
|
||||
assert.Equal(expected, parsed)
|
||||
// The config should be updated now.
|
||||
{
|
||||
req, _ := http.NewRequest("GET", "/v1/connect/ca/configuration", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.ConnectCAConfiguration(resp, req)
|
||||
require.NoError(err)
|
||||
|
||||
got := obj.(structs.CAConfiguration)
|
||||
// Reset Raft indexes to make it non flaky
|
||||
got.CreateIndex = 0
|
||||
got.ModifyIndex = 0
|
||||
require.Equal(tc.wantCfg, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -244,6 +244,26 @@ func (s *ConnectCA) ConfigurationSet(
|
|||
// either by swapping the provider type or changing the provider's config
|
||||
// to use a different root certificate.
|
||||
|
||||
// First up, sanity check that the current provider actually supports
|
||||
// cross-signing.
|
||||
oldProvider, _ := s.srv.getCAProvider()
|
||||
if oldProvider == nil {
|
||||
return fmt.Errorf("internal error: CA provider is nil")
|
||||
}
|
||||
canXSign, err := oldProvider.SupportsCrossSigning()
|
||||
if err != nil {
|
||||
return fmt.Errorf("CA provider error: %s", err)
|
||||
}
|
||||
if !canXSign && !args.Config.ForceWithoutCrossSigning {
|
||||
return errors.New("The current CA Provider does not support cross-signing. " +
|
||||
"You can try again with ForceWithoutCrossSigningSet but this may cause " +
|
||||
"disruption - see documentation for more.")
|
||||
}
|
||||
if !canXSign && args.Config.ForceWithoutCrossSigning {
|
||||
s.srv.logger.Println("[WARN] current CA doesn't support cross signing but " +
|
||||
"CA reconfiguration forced anyway with ForceWithoutCrossSigning")
|
||||
}
|
||||
|
||||
// If it's a config change that would trigger a rotation (different provider/root):
|
||||
// 1. Get the root from the new provider.
|
||||
// 2. Call CrossSignCA on the old provider to sign the new root with the old one to
|
||||
|
@ -255,18 +275,18 @@ func (s *ConnectCA) ConfigurationSet(
|
|||
return err
|
||||
}
|
||||
|
||||
// Have the old provider cross-sign the new intermediate
|
||||
oldProvider, _ := s.srv.getCAProvider()
|
||||
if oldProvider == nil {
|
||||
return fmt.Errorf("internal error: CA provider is nil")
|
||||
}
|
||||
xcCert, err := oldProvider.CrossSignCA(newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
if canXSign {
|
||||
// Have the old provider cross-sign the new root
|
||||
xcCert, err := oldProvider.CrossSignCA(newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the cross signed cert to the new CA's intermediates (to be attached
|
||||
// to leaf certs).
|
||||
newActiveRoot.IntermediateCerts = []string{xcCert}
|
||||
}
|
||||
|
||||
// Add the cross signed cert to the new root's intermediates.
|
||||
newActiveRoot.IntermediateCerts = []string{xcCert}
|
||||
intermediate, err := newProvider.GenerateIntermediate()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -144,6 +144,112 @@ func TestConnectCAConfig_GetSet(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// This test case tests that the logic around forcing a rotation without cross
|
||||
// signing works when requested (and is denied when not requested). This occurs
|
||||
// if the current CA is not able to cross sign external CA certificates.
|
||||
func TestConnectCAConfig_GetSetForceNoCrossSigning(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require := require.New(t)
|
||||
// Setup a server with a built-in CA that as artificially disabled cross
|
||||
// signing. This is simpler than running tests with external CA dependencies.
|
||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.CAConfig.Config["DisableCrossSigning"] = true
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
codec := rpcClient(t, s1)
|
||||
defer codec.Close()
|
||||
|
||||
testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
|
||||
|
||||
// Store the current root
|
||||
rootReq := &structs.DCSpecificRequest{
|
||||
Datacenter: "dc1",
|
||||
}
|
||||
var rootList structs.IndexedCARoots
|
||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", rootReq, &rootList))
|
||||
require.Len(rootList.Roots, 1)
|
||||
oldRoot := rootList.Roots[0]
|
||||
|
||||
// Get the starting config
|
||||
{
|
||||
args := &structs.DCSpecificRequest{
|
||||
Datacenter: "dc1",
|
||||
}
|
||||
var reply structs.CAConfiguration
|
||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationGet", args, &reply))
|
||||
|
||||
actual, err := ca.ParseConsulCAConfig(reply.Config)
|
||||
require.NoError(err)
|
||||
expected, err := ca.ParseConsulCAConfig(s1.config.CAConfig.Config)
|
||||
require.NoError(err)
|
||||
require.Equal(reply.Provider, s1.config.CAConfig.Provider)
|
||||
require.Equal(actual, expected)
|
||||
}
|
||||
|
||||
// Update to a new CA with different key. This should fail since the existing
|
||||
// CA doesn't support cross signing so can't rotate safely.
|
||||
_, newKey, err := connect.GeneratePrivateKey()
|
||||
require.NoError(err)
|
||||
newConfig := &structs.CAConfiguration{
|
||||
Provider: "consul",
|
||||
Config: map[string]interface{}{
|
||||
"PrivateKey": newKey,
|
||||
},
|
||||
}
|
||||
{
|
||||
args := &structs.CARequest{
|
||||
Datacenter: "dc1",
|
||||
Config: newConfig,
|
||||
}
|
||||
var reply interface{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", args, &reply)
|
||||
require.EqualError(err, "The current CA Provider does not support cross-signing. "+
|
||||
"You can try again with ForceWithoutCrossSigningSet but this may cause disruption"+
|
||||
" - see documentation for more.")
|
||||
}
|
||||
|
||||
// Now try again with the force flag set and it should work
|
||||
{
|
||||
newConfig.ForceWithoutCrossSigning = true
|
||||
args := &structs.CARequest{
|
||||
Datacenter: "dc1",
|
||||
Config: newConfig,
|
||||
}
|
||||
var reply interface{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", args, &reply)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
// Make sure the new root has been added but with no cross-signed intermediate
|
||||
{
|
||||
args := &structs.DCSpecificRequest{
|
||||
Datacenter: "dc1",
|
||||
}
|
||||
var reply structs.IndexedCARoots
|
||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", args, &reply))
|
||||
require.Len(reply.Roots, 2)
|
||||
|
||||
for _, r := range reply.Roots {
|
||||
if r.ID == oldRoot.ID {
|
||||
// The old root should no longer be marked as the active root,
|
||||
// and none of its other fields should have changed.
|
||||
require.False(r.Active)
|
||||
require.Equal(r.Name, oldRoot.Name)
|
||||
require.Equal(r.RootCert, oldRoot.RootCert)
|
||||
require.Equal(r.SigningCert, oldRoot.SigningCert)
|
||||
require.Equal(r.IntermediateCerts, oldRoot.IntermediateCerts)
|
||||
} else {
|
||||
// The new root should NOT have a valid cross-signed cert from the old
|
||||
// root as an intermediate.
|
||||
require.True(r.Active)
|
||||
require.Empty(r.IntermediateCerts)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectCAConfig_TriggerRotation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
@ -244,6 +245,14 @@ type CAConfiguration struct {
|
|||
// identifiers anyway so this is simpler.
|
||||
State map[string]string
|
||||
|
||||
// ForceWithoutCrossSigning indicates that the CA reconfiguration should go
|
||||
// ahead even if the current CA is unable to cross sign certificates. This
|
||||
// risks temporary connection failures during the rollout as new leafs will be
|
||||
// rejected by proxies that have not yet observed the new root cert but is the
|
||||
// only option if a CA that doesn't support cross signing needs to be
|
||||
// reconfigured or mirated away from.
|
||||
ForceWithoutCrossSigning bool
|
||||
|
||||
RaftIndex
|
||||
}
|
||||
|
||||
|
@ -287,6 +296,25 @@ func (c *CAConfiguration) UnmarshalBinary(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CAConfiguration) UnmarshalJSON(data []byte) (err error) {
|
||||
type Alias CAConfiguration
|
||||
|
||||
aux := &struct {
|
||||
ForceWithoutCrossSigningSnake bool `json:"force_without_cross_signing"`
|
||||
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(c),
|
||||
}
|
||||
if err = json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
if aux.ForceWithoutCrossSigningSnake {
|
||||
c.ForceWithoutCrossSigning = aux.ForceWithoutCrossSigningSnake
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -398,6 +426,12 @@ type ConsulCAProviderConfig struct {
|
|||
PrivateKey string
|
||||
RootCert string
|
||||
RotationPeriod time.Duration
|
||||
|
||||
// DisableCrossSigning is really only useful in test code to use the built in
|
||||
// provider while exercising logic that depends on the CA provider ability to
|
||||
// cross sign. We don't document this config field publicly or make any
|
||||
// attempt to parse it from snake case unlike other fields here.
|
||||
DisableCrossSigning bool
|
||||
}
|
||||
|
||||
// CAConsulProviderState is used to track the built-in Consul CA provider's state.
|
||||
|
|
|
@ -132,6 +132,13 @@ The table below shows this endpoint's support for
|
|||
for the chosen provider. For more information on configuring the Connect CA
|
||||
providers, see [Provider Config](/docs/connect/ca.html).
|
||||
|
||||
- `ForceWithoutCrossSigning` `(bool: <optional>)` - Indicates that the CA change
|
||||
should be force to complete even if the current CA doesn't support cross
|
||||
signing. Changing root without cross-signing may cause temporary connection
|
||||
failures until the rollout completes. See [Forced Rotation Without
|
||||
Cross-Signing](/docs/connect/ca.html#forced-rotation-without-cross-signing)
|
||||
for more detail.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
@ -142,7 +149,8 @@ providers, see [Provider Config](/docs/connect/ca.html).
|
|||
"PrivateKey": "-----BEGIN RSA PRIVATE KEY-----...",
|
||||
"RootCert": "-----BEGIN CERTIFICATE-----...",
|
||||
"RotationPeriod": "2160h"
|
||||
}
|
||||
},
|
||||
"ForceWithoutCrossSigning": false
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@ Usage: `consul connect ca set-config [options]`
|
|||
#### Command Options
|
||||
|
||||
* `-config-file` - (required) Specifies a JSON-formatted file to use for the new configuration.
|
||||
The format of this config file matches the request payload documented in the
|
||||
[Update CA Configuration API](/api/connect/ca.html#update-ca-configuration).
|
||||
|
||||
The output looks like this:
|
||||
|
||||
|
|
|
@ -107,30 +107,37 @@ CA provider documentation in the sidebar to the left.
|
|||
## Root Certificate Rotation
|
||||
|
||||
Whenever the CA's configuration is updated in a way that causes the root key to
|
||||
change, a special rotation process will be triggered in order to smoothly transition to
|
||||
the new certificate. This rotation is automatically orchestrated by Consul.
|
||||
change, a special rotation process will be triggered in order to smoothly
|
||||
transition to the new certificate. This rotation is automatically orchestrated
|
||||
by Consul.
|
||||
|
||||
~> If the current CA Provider doesn't support cross-signing, this process can't
|
||||
be followed. See [Forced Rotation Without
|
||||
Cross-Signing](#forced-rotation-without-cross-signing).
|
||||
|
||||
This also automatically occurs when a completely different CA provider is
|
||||
configured (since this changes the root key). Therefore, this automatic rotation
|
||||
process can also be used to cleanly transition between CA providers. For example,
|
||||
updating Connect to use Vault instead of the built-in CA.
|
||||
|
||||
During rotation, an intermediate CA certificate is requested from the new root, which is then
|
||||
cross-signed by the old root. This cross-signed certificate is then distributed
|
||||
alongside any newly-generated leaf certificates used by the proxies once the new root
|
||||
becomes active, and provides a chain of trust back to the old root certificate in the
|
||||
event that a certificate signed by the new root is presented to a proxy that has not yet
|
||||
updated its bundle of trusted root CA certificates to include the new root.
|
||||
During rotation, an intermediate CA certificate is requested from the new root,
|
||||
which is then cross-signed by the old root. This cross-signed certificate is
|
||||
then distributed alongside any newly-generated leaf certificates used by the
|
||||
proxies once the new root becomes active, and provides a chain of trust back to
|
||||
the old root certificate in the event that a certificate signed by the new root
|
||||
is presented to a proxy that has not yet updated its bundle of trusted root CA
|
||||
certificates to include the new root.
|
||||
|
||||
After the cross-signed certificate has been successfully generated and the new root
|
||||
certificate or CA provider has been set up, the new root becomes the active one
|
||||
and is immediately used for signing any new incoming certificate requests.
|
||||
|
||||
If we check the [list CA roots endpoint](/api/connect/ca.html#list-ca-root-certificates)
|
||||
after updating the configuration with a new root certificate, we can see both the old and new root
|
||||
certificates are present, and the currently active root has an intermediate certificate
|
||||
which has been generated and cross-signed automatically by the old root during the
|
||||
rotation process:
|
||||
If we check the [list CA roots
|
||||
endpoint](/api/connect/ca.html#list-ca-root-certificates) after updating the
|
||||
configuration with a new root certificate, we can see both the old and new root
|
||||
certificates are present, and the currently active root has an intermediate
|
||||
certificate which has been generated and cross-signed automatically by the old
|
||||
root during the rotation process:
|
||||
|
||||
```bash
|
||||
$ curl localhost:8500/v1/connect/ca/roots
|
||||
|
@ -178,3 +185,30 @@ $ curl localhost:8500/v1/connect/ca/roots
|
|||
|
||||
The old root certificate will be automatically removed once enough time has elapsed
|
||||
for any leaf certificates signed by it to expire.
|
||||
|
||||
### Forced Rotation Without Cross-Signing
|
||||
|
||||
If the CA provider that is currently in use does not support cross-signing, then
|
||||
attempts to change the root key or CA provider will fail. This is to ensure
|
||||
operators don't make the change without understanding that there is additional
|
||||
risk involved.
|
||||
|
||||
It is possible to force the change to happen anyway by setting the
|
||||
`ForceWithoutCrossSigning` field in the CA configuration to `true`.
|
||||
|
||||
The downside is that all new certificates will immediately start being signed
|
||||
with the new root key, but it will take some time for agents throughout the
|
||||
cluster to observe the root CA change and reconfigure applications and proxies
|
||||
to accept certificates signed by this new root. This will mean connections made
|
||||
with a new certificate may fail for a short period after the CA change.
|
||||
|
||||
Typically all connected agents will have observed the new roots within seconds
|
||||
even in a large deployment so the impact should be contained. But it is possible
|
||||
for a disconnected, overloaded or misconfigured agent to not see the new root
|
||||
for an unbounded amount of time during which new connections to services on that
|
||||
host will fail. The issue will resolve as soon as the agent can reconnect to
|
||||
servers.
|
||||
|
||||
Currently both Consul and Vault CA providers _do_ support cross signing. As more
|
||||
providers are added this documentation will list any that this section applies
|
||||
to.
|
Loading…
Reference in New Issue