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:
Paul Banks 2019-11-11 21:36:22 +00:00 committed by GitHub
parent ca96d5fa72
commit 1197b43c7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 657 additions and 119 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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 {}

View File

@ -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)

View File

@ -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{}{})
}

View File

@ -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.

View File

@ -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) {

View File

@ -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.

View File

@ -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{}

View File

@ -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.
tests := []struct {
name string
body string
wantErr bool
wantCfg structs.CAConfiguration
}{
{
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(`
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,
},
},
}
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")
// 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)
assert.NoError(err)
if tc.wantErr {
require.Error(err)
return
}
require.NoError(err)
}
// The config should be updated now.
{
expected.RotationPeriod = time.Hour
req, _ := http.NewRequest("GET", "/v1/connect/ca/configuration", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.ConnectCAConfiguration(resp, req)
assert.NoError(err)
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)
got := obj.(structs.CAConfiguration)
// Reset Raft indexes to make it non flaky
got.CreateIndex = 0
got.ModifyIndex = 0
require.Equal(tc.wantCfg, got)
}
})
}
}

View File

@ -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")
}
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 root's intermediates.
// Add the cross signed cert to the new CA's intermediates (to be attached
// to leaf certs).
newActiveRoot.IntermediateCerts = []string{xcCert}
}
intermediate, err := newProvider.GenerateIntermediate()
if err != nil {
return err

View File

@ -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()

View File

@ -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.

View File

@ -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
}
```

View File

@ -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:

View File

@ -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.