From f2797851835150340db8d982f7e029e80d7caa06 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Fri, 18 Nov 2016 16:53:28 -0800 Subject: [PATCH 1/4] Added rootCABundle on NodeStatusResponse Signed-off-by: Diogo Monica --- api/ca.pb.go | 137 ++++++++++++++++++++++++++++++++++----------------- api/ca.proto | 1 + 2 files changed, 93 insertions(+), 45 deletions(-) diff --git a/api/ca.pb.go b/api/ca.pb.go index 619421b3b0..d1c13f2c45 100644 --- a/api/ca.pb.go +++ b/api/ca.pb.go @@ -43,8 +43,9 @@ func (*NodeCertificateStatusRequest) ProtoMessage() {} func (*NodeCertificateStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorCa, []int{0} } type NodeCertificateStatusResponse struct { - Status *IssuanceStatus `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` - Certificate *Certificate `protobuf:"bytes,2,opt,name=certificate" json:"certificate,omitempty"` + Status *IssuanceStatus `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + Certificate *Certificate `protobuf:"bytes,2,opt,name=certificate" json:"certificate,omitempty"` + RootCABundle []byte `protobuf:"bytes,3,opt,name=root_ca_bundle,json=rootCaBundle,proto3" json:"root_ca_bundle,omitempty"` } func (m *NodeCertificateStatusResponse) Reset() { *m = NodeCertificateStatusResponse{} } @@ -183,8 +184,9 @@ func (m *NodeCertificateStatusResponse) Copy() *NodeCertificateStatusResponse { } o := &NodeCertificateStatusResponse{ - Status: m.Status.Copy(), - Certificate: m.Certificate.Copy(), + Status: m.Status.Copy(), + Certificate: m.Certificate.Copy(), + RootCABundle: m.RootCABundle, } return o @@ -277,7 +279,7 @@ func (this *NodeCertificateStatusResponse) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 6) + s := make([]string, 0, 7) s = append(s, "&api.NodeCertificateStatusResponse{") if this.Status != nil { s = append(s, "Status: "+fmt.Sprintf("%#v", this.Status)+",\n") @@ -285,6 +287,7 @@ func (this *NodeCertificateStatusResponse) GoString() string { if this.Certificate != nil { s = append(s, "Certificate: "+fmt.Sprintf("%#v", this.Certificate)+",\n") } + s = append(s, "RootCABundle: "+fmt.Sprintf("%#v", this.RootCABundle)+",\n") s = append(s, "}") return strings.Join(s, "") } @@ -643,6 +646,12 @@ func (m *NodeCertificateStatusResponse) MarshalTo(data []byte) (int, error) { } i += n2 } + if len(m.RootCABundle) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintCa(data, i, uint64(len(m.RootCABundle))) + i += copy(data[i:], m.RootCABundle) + } return i, nil } @@ -1112,6 +1121,10 @@ func (m *NodeCertificateStatusResponse) Size() (n int) { l = m.Certificate.Size() n += 1 + l + sovCa(uint64(l)) } + l = len(m.RootCABundle) + if l > 0 { + n += 1 + l + sovCa(uint64(l)) + } return n } @@ -1212,6 +1225,7 @@ func (this *NodeCertificateStatusResponse) String() string { s := strings.Join([]string{`&NodeCertificateStatusResponse{`, `Status:` + strings.Replace(fmt.Sprintf("%v", this.Status), "IssuanceStatus", "IssuanceStatus", 1) + `,`, `Certificate:` + strings.Replace(fmt.Sprintf("%v", this.Certificate), "Certificate", "Certificate", 1) + `,`, + `RootCABundle:` + fmt.Sprintf("%v", this.RootCABundle) + `,`, `}`, }, "") return s @@ -1461,6 +1475,37 @@ func (m *NodeCertificateStatusResponse) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RootCABundle", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCa + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCa + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RootCABundle = append(m.RootCABundle[:0], data[iNdEx:postIndex]...) + if m.RootCABundle == nil { + m.RootCABundle = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCa(data[iNdEx:]) @@ -2128,44 +2173,46 @@ var ( func init() { proto.RegisterFile("ca.proto", fileDescriptorCa) } var fileDescriptorCa = []byte{ - // 615 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0xed, 0xb8, 0xfd, 0xd2, 0xf6, 0x26, 0x5f, 0x8b, 0xa6, 0xad, 0x14, 0xd2, 0xd4, 0xa9, 0xcc, - 0xa2, 0x65, 0x81, 0xd3, 0x06, 0x56, 0xb0, 0x21, 0x09, 0x52, 0x15, 0xa1, 0x22, 0x34, 0x11, 0x6c, - 0x2b, 0xc7, 0x19, 0x82, 0x15, 0xc7, 0x63, 0x3c, 0xe3, 0x40, 0x76, 0x48, 0x20, 0xde, 0x00, 0xc1, - 0x8a, 0x47, 0xe0, 0x39, 0x22, 0x56, 0x48, 0x6c, 0x58, 0x45, 0xc4, 0x0f, 0x80, 0x78, 0x04, 0xe4, - 0xb1, 0x4d, 0xf3, 0xe3, 0x84, 0xb2, 0xf2, 0xcc, 0x9d, 0x73, 0xce, 0xbd, 0xf7, 0xcc, 0xf5, 0xc0, - 0x86, 0x69, 0xe8, 0xae, 0xc7, 0x04, 0xc3, 0xb8, 0xcd, 0xcc, 0x2e, 0xf5, 0x74, 0xfe, 0xd2, 0xf0, - 0x7a, 0x5d, 0x4b, 0xe8, 0xfd, 0xd3, 0x42, 0x56, 0x0c, 0x5c, 0xca, 0x23, 0x40, 0x21, 0xcb, 0x5d, - 0x6a, 0x26, 0x9b, 0xdd, 0x0e, 0xeb, 0x30, 0xb9, 0x2c, 0x87, 0xab, 0x38, 0xba, 0xe3, 0xda, 0x7e, - 0xc7, 0x72, 0xca, 0xd1, 0x27, 0x0a, 0x6a, 0x75, 0x28, 0x3e, 0x62, 0x6d, 0x5a, 0xa7, 0x9e, 0xb0, - 0x9e, 0x59, 0xa6, 0x21, 0x68, 0x53, 0x18, 0xc2, 0xe7, 0x84, 0xbe, 0xf0, 0x29, 0x17, 0xf8, 0x06, - 0xac, 0x3b, 0xac, 0x4d, 0x2f, 0xac, 0x76, 0x1e, 0x1d, 0xa2, 0xe3, 0xcd, 0x1a, 0x04, 0xa3, 0x52, - 0x26, 0xa4, 0x34, 0x1e, 0x90, 0x4c, 0x78, 0xd4, 0x68, 0x6b, 0x9f, 0x10, 0x1c, 0x2c, 0x50, 0xe1, - 0x2e, 0x73, 0x38, 0xc5, 0x77, 0x21, 0xc3, 0x65, 0x44, 0xaa, 0x64, 0x2b, 0x9a, 0x3e, 0xdf, 0x90, - 0xde, 0xe0, 0xdc, 0x37, 0x1c, 0x33, 0xe1, 0xc6, 0x0c, 0x5c, 0x85, 0xac, 0x79, 0x29, 0x9c, 0x57, - 0xa4, 0x40, 0x29, 0x4d, 0x60, 0x22, 0x3f, 0x99, 0xe4, 0x68, 0xdf, 0x10, 0xec, 0x87, 0xea, 0x74, - 0xa6, 0xca, 0xa4, 0xcb, 0x3b, 0xb0, 0xe6, 0x31, 0x9b, 0xca, 0xe2, 0xb6, 0x2a, 0xc5, 0x34, 0xed, - 0x90, 0x49, 0x98, 0x4d, 0x6b, 0x4a, 0x1e, 0x11, 0x89, 0xc6, 0xd7, 0x61, 0xd5, 0xe4, 0x9e, 0x2c, - 0x28, 0x57, 0x5b, 0x0f, 0x46, 0xa5, 0xd5, 0x7a, 0x93, 0x90, 0x30, 0x86, 0x77, 0xe1, 0x3f, 0xc1, - 0xba, 0xd4, 0xc9, 0xaf, 0x86, 0xa6, 0x91, 0x68, 0x83, 0xcf, 0x21, 0x67, 0xf4, 0x0d, 0xcb, 0x36, - 0x5a, 0x96, 0x6d, 0x89, 0x41, 0x7e, 0x4d, 0xa6, 0xbb, 0xb9, 0x28, 0x5d, 0xd3, 0xa5, 0xa6, 0x5e, - 0x9d, 0x20, 0x90, 0x29, 0xba, 0xf6, 0x1e, 0x41, 0x31, 0xbd, 0xab, 0xd8, 0xf5, 0xab, 0x5c, 0x1e, - 0x7e, 0x0c, 0xdb, 0x12, 0xd4, 0xa3, 0xbd, 0x16, 0xf5, 0xf8, 0x73, 0xcb, 0x95, 0x1d, 0x6d, 0x55, - 0x8e, 0x96, 0xd6, 0x75, 0xfe, 0x07, 0x4e, 0xb6, 0x42, 0xfe, 0xe5, 0x5e, 0x3b, 0x80, 0xfd, 0x33, - 0x2a, 0x08, 0x63, 0xa2, 0x5e, 0x9d, 0x37, 0x5b, 0xbb, 0x0f, 0xc5, 0xf4, 0xe3, 0xb8, 0xea, 0xc3, - 0xe9, 0xfb, 0x0e, 0x2b, 0xcf, 0x4d, 0x5f, 0xe7, 0x1e, 0xec, 0x9c, 0x51, 0xf1, 0xc4, 0xb1, 0x99, - 0xd9, 0x7d, 0x48, 0x07, 0x89, 0xb0, 0x07, 0xbb, 0xd3, 0xe1, 0x58, 0xf0, 0x00, 0xc0, 0x97, 0xc1, - 0x8b, 0x2e, 0x1d, 0xc4, 0x7a, 0x9b, 0x7e, 0x02, 0xc3, 0xf7, 0x60, 0xbd, 0x4f, 0x3d, 0x6e, 0x31, - 0x27, 0x9e, 0xad, 0xfd, 0xb4, 0xc6, 0x9f, 0x46, 0x90, 0xda, 0xda, 0x70, 0x54, 0x5a, 0x21, 0x09, - 0xa3, 0xf2, 0x56, 0x01, 0xa5, 0x5e, 0xc5, 0x6f, 0x90, 0xcc, 0x3d, 0xd7, 0x14, 0x2e, 0xa7, 0x69, - 0x2d, 0x71, 0xa7, 0x70, 0x72, 0x75, 0x42, 0xd4, 0x9e, 0xb6, 0xf1, 0xe5, 0xf3, 0xcf, 0x8f, 0x8a, - 0x72, 0x0d, 0xe1, 0x57, 0x90, 0x9b, 0x34, 0x00, 0x1f, 0x2d, 0xd0, 0x9a, 0x75, 0xae, 0x70, 0xfc, - 0x77, 0x60, 0x9c, 0x6c, 0x4f, 0x26, 0xdb, 0x86, 0xff, 0x25, 0xf2, 0x56, 0xcf, 0x70, 0x8c, 0x0e, - 0xf5, 0x2a, 0x1f, 0x14, 0x90, 0x73, 0x15, 0x5b, 0x91, 0x36, 0x95, 0xe9, 0x56, 0x2c, 0xf9, 0x2b, - 0xd3, 0xad, 0x58, 0x36, 0xf0, 0x13, 0x56, 0xbc, 0x43, 0xb0, 0x97, 0xfa, 0x24, 0xe1, 0x93, 0x45, - 0x63, 0xbd, 0xe8, 0x0d, 0x2c, 0x9c, 0xfe, 0x03, 0x63, 0xb6, 0x90, 0x5a, 0x71, 0x38, 0x56, 0x57, - 0xbe, 0x8f, 0xd5, 0x95, 0x5f, 0x63, 0x15, 0xbd, 0x0e, 0x54, 0x34, 0x0c, 0x54, 0xf4, 0x35, 0x50, - 0xd1, 0x8f, 0x40, 0x45, 0xad, 0x8c, 0x7c, 0x85, 0x6f, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xcf, - 0xc4, 0x68, 0xc2, 0xea, 0x05, 0x00, 0x00, + // 651 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xc1, 0x6e, 0xd3, 0x4a, + 0x14, 0xed, 0xb8, 0x7d, 0x69, 0x7b, 0xe3, 0x97, 0x56, 0xd3, 0x56, 0x0a, 0x69, 0xea, 0x54, 0x66, + 0xd1, 0xb2, 0x20, 0x6d, 0x03, 0x62, 0x01, 0x1b, 0xe2, 0x20, 0x55, 0x15, 0x2a, 0x42, 0x53, 0xc1, + 0x36, 0x9a, 0x38, 0x43, 0xb0, 0xe2, 0x78, 0x8c, 0x67, 0x5c, 0xc8, 0x0e, 0x09, 0xc4, 0x1f, 0x20, + 0xf8, 0x0a, 0xbe, 0xa3, 0x62, 0x85, 0x84, 0x84, 0x58, 0x45, 0xd4, 0x1f, 0x80, 0xf8, 0x04, 0xe4, + 0xb1, 0x43, 0x93, 0xc6, 0x09, 0x65, 0x15, 0xcf, 0xf5, 0x39, 0xe7, 0xde, 0x73, 0x7c, 0x33, 0xb0, + 0x64, 0xd3, 0xaa, 0x1f, 0x70, 0xc9, 0x31, 0x6e, 0x73, 0xbb, 0xcb, 0x82, 0xaa, 0x78, 0x49, 0x83, + 0x5e, 0xd7, 0x91, 0xd5, 0xd3, 0x83, 0x52, 0x5e, 0xf6, 0x7d, 0x26, 0x12, 0x40, 0x29, 0x2f, 0x7c, + 0x66, 0x0f, 0x0f, 0xeb, 0x1d, 0xde, 0xe1, 0xea, 0x71, 0x2f, 0x7e, 0x4a, 0xab, 0x6b, 0xbe, 0x1b, + 0x76, 0x1c, 0x6f, 0x2f, 0xf9, 0x49, 0x8a, 0x66, 0x03, 0xca, 0x8f, 0x78, 0x9b, 0x35, 0x58, 0x20, + 0x9d, 0x67, 0x8e, 0x4d, 0x25, 0x3b, 0x91, 0x54, 0x86, 0x82, 0xb0, 0x17, 0x21, 0x13, 0x12, 0x5f, + 0x87, 0x45, 0x8f, 0xb7, 0x59, 0xd3, 0x69, 0x17, 0xd1, 0x36, 0xda, 0x5d, 0xb6, 0x20, 0x1a, 0x54, + 0x72, 0x31, 0xe5, 0xe8, 0x01, 0xc9, 0xc5, 0xaf, 0x8e, 0xda, 0xe6, 0x37, 0x04, 0x5b, 0x53, 0x54, + 0x84, 0xcf, 0x3d, 0xc1, 0xf0, 0x5d, 0xc8, 0x09, 0x55, 0x51, 0x2a, 0xf9, 0x9a, 0x59, 0x9d, 0x34, + 0x54, 0x3d, 0x12, 0x22, 0xa4, 0x9e, 0x3d, 0xe4, 0xa6, 0x0c, 0x5c, 0x87, 0xbc, 0x7d, 0x21, 0x5c, + 0xd4, 0x94, 0x40, 0x25, 0x4b, 0x60, 0xa4, 0x3f, 0x19, 0xe5, 0xe0, 0x3b, 0x50, 0x08, 0x38, 0x97, + 0x4d, 0x9b, 0x36, 0x5b, 0xa1, 0xd7, 0x76, 0x59, 0x71, 0x7e, 0x1b, 0xed, 0xea, 0xd6, 0x6a, 0x34, + 0xa8, 0xe8, 0x84, 0x73, 0xd9, 0xa8, 0x5b, 0xaa, 0x4e, 0xf4, 0x18, 0xd7, 0xa0, 0xc9, 0xc9, 0xfc, + 0x8a, 0x60, 0x33, 0x9e, 0x8a, 0x5d, 0x72, 0x37, 0x4c, 0xe7, 0x36, 0x2c, 0x04, 0xdc, 0x65, 0xca, + 0x54, 0xa1, 0x56, 0xce, 0x9a, 0x29, 0x66, 0x12, 0xee, 0x32, 0x4b, 0x2b, 0x22, 0xa2, 0xd0, 0xf8, + 0x1a, 0xcc, 0xdb, 0x22, 0x50, 0x46, 0x74, 0x6b, 0x31, 0x1a, 0x54, 0xe6, 0x1b, 0x27, 0x84, 0xc4, + 0x35, 0xbc, 0x0e, 0xff, 0x49, 0xde, 0x65, 0x9e, 0x9a, 0x6f, 0x99, 0x24, 0x07, 0x7c, 0x0c, 0x3a, + 0x3d, 0xa5, 0x8e, 0x4b, 0x5b, 0x8e, 0xeb, 0xc8, 0x7e, 0x71, 0x41, 0xb5, 0xbb, 0x31, 0xad, 0xdd, + 0x89, 0xcf, 0xec, 0x6a, 0x7d, 0x84, 0x40, 0xc6, 0xe8, 0xe6, 0x7b, 0x04, 0xe5, 0x6c, 0x57, 0xe9, + 0xd7, 0xba, 0xca, 0x47, 0xc7, 0x8f, 0x61, 0x45, 0x81, 0x7a, 0xac, 0xd7, 0x62, 0x81, 0x78, 0xee, + 0xf8, 0xca, 0x51, 0xa1, 0xb6, 0x33, 0x73, 0xae, 0xe3, 0x3f, 0x70, 0x52, 0x88, 0xf9, 0x17, 0x67, + 0x73, 0x0b, 0x36, 0x0f, 0x99, 0x4c, 0x3e, 0xc7, 0x64, 0xd8, 0xe6, 0x7d, 0x28, 0x67, 0xbf, 0x4e, + 0xa7, 0xde, 0x1e, 0xdf, 0x93, 0x78, 0x72, 0x7d, 0x6c, 0x0d, 0xcc, 0x0d, 0x58, 0x3b, 0x64, 0xf2, + 0x89, 0xe7, 0x72, 0xbb, 0xfb, 0x90, 0xf5, 0x87, 0xc2, 0x01, 0xac, 0x8f, 0x97, 0x53, 0xc1, 0x2d, + 0x80, 0x50, 0x15, 0x9b, 0x5d, 0xd6, 0x4f, 0xf5, 0x96, 0xc3, 0x21, 0x0c, 0xdf, 0x83, 0xc5, 0x53, + 0x16, 0x08, 0x87, 0x7b, 0xe9, 0x4e, 0x6e, 0x66, 0x19, 0x7f, 0x9a, 0x40, 0xac, 0x85, 0xb3, 0x41, + 0x65, 0x8e, 0x0c, 0x19, 0xb5, 0xb7, 0x1a, 0x68, 0x8d, 0x3a, 0x7e, 0x83, 0x54, 0xef, 0x09, 0x53, + 0x78, 0x2f, 0x4b, 0x6b, 0x46, 0x3a, 0xa5, 0xfd, 0xab, 0x13, 0x12, 0x7b, 0xe6, 0xd2, 0xe7, 0x4f, + 0x3f, 0x3f, 0x6a, 0xda, 0x2a, 0xc2, 0xaf, 0x40, 0x1f, 0x0d, 0x00, 0xef, 0x4c, 0xd1, 0xba, 0x9c, + 0x5c, 0x69, 0xf7, 0xef, 0xc0, 0xb4, 0xd9, 0x86, 0x6a, 0xb6, 0x02, 0xff, 0x2b, 0xe4, 0xcd, 0x1e, + 0xf5, 0x68, 0x87, 0x05, 0xb5, 0x0f, 0x1a, 0xa8, 0xbd, 0x4a, 0xa3, 0xc8, 0xda, 0xca, 0xec, 0x28, + 0x66, 0xfc, 0x2b, 0xb3, 0xa3, 0x98, 0xb5, 0xf0, 0x23, 0x51, 0xbc, 0x43, 0xb0, 0x91, 0x79, 0x95, + 0xe1, 0xfd, 0x69, 0x6b, 0x3d, 0xed, 0xee, 0x2c, 0x1d, 0xfc, 0x03, 0xe3, 0xf2, 0x20, 0x56, 0xf9, + 0xec, 0xdc, 0x98, 0xfb, 0x7e, 0x6e, 0xcc, 0xfd, 0x3a, 0x37, 0xd0, 0xeb, 0xc8, 0x40, 0x67, 0x91, + 0x81, 0xbe, 0x44, 0x06, 0xfa, 0x11, 0x19, 0xa8, 0x95, 0x53, 0xb7, 0xf7, 0xad, 0xdf, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x69, 0xb6, 0x7e, 0x90, 0x22, 0x06, 0x00, 0x00, } diff --git a/api/ca.proto b/api/ca.proto index be4b4a58c9..1d1cfa2193 100644 --- a/api/ca.proto +++ b/api/ca.proto @@ -36,6 +36,7 @@ message NodeCertificateStatusRequest { message NodeCertificateStatusResponse { IssuanceStatus status = 1; Certificate certificate = 2; + bytes root_ca_bundle = 3 [(gogoproto.customname) = "RootCABundle"]; } message IssueNodeCertificateRequest { From 58670647dc114d4ffd303d2026e048bb139da58a Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Fri, 18 Nov 2016 16:55:02 -0800 Subject: [PATCH 2/4] Added path to RootCA, changed creation params, return current bundle on NodeStatus Signed-off-by: Diogo Monica --- ca/certificates.go | 104 ++++++++++++++++++++++++++------------------- ca/config.go | 13 ++++-- ca/server.go | 10 +++-- 3 files changed, 77 insertions(+), 50 deletions(-) diff --git a/ca/certificates.go b/ca/certificates.go index 6eb6e4dd74..6c94005228 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -102,13 +102,15 @@ type RootCA struct { // Key will only be used by the original manager to put the private // key-material in raft, no signing operations depend on it. Key []byte - // Cert includes the PEM encoded Certificate for the Root CA + // Cert includes the PEM encoded Certificate bundle for the Root CA Cert []byte Pool *x509.CertPool // Digest of the serialized bytes of the certificate Digest digest.Digest // This signer will be nil if the node doesn't have the appropriate key material Signer cfsigner.Signer + // Stores the location on disk where the RootCA lives + Path CertPaths } // CanSign ensures that the signer has all three necessary elements needed to operate @@ -163,9 +165,9 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit // Get the remote manager to issue a CA signed certificate for this node // Retry up to 5 times in case the manager we first try to contact isn't // responding properly (for example, it may have just been demoted). - var signedCert []byte + var signedCert, rootCABundle []byte for i := 0; i != 5; i++ { - signedCert, err = GetRemoteSignedCertificate(ctx, csr, rca.Pool, config) + signedCert, rootCABundle, err = GetRemoteSignedCertificate(ctx, csr, rca.Pool, config) if err == nil { break } @@ -185,11 +187,22 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit if err != nil { return nil, err } - // Include our current root pool + // We retrieve the certificate with the current root pool, so we know this was issued by a legitimate manager. + // However, there might have been a server-side root rotation, so we verify this cert with a new pool. + // If we got a valid rootCABundle, turn it into a Pool, and verify the newly minted certificate using it. + rootCAPool := rca.Pool + newRootCA, newRootErr := NewRootCA(rootCABundle, nil, rca.Path, time.Minute) + if newRootErr == nil { + // The rootCABundle we got from the remote server seems to be good, use it + rootCAPool = newRootCA.Pool + } + + // Create VerifyOptions with either the new certificate bundle, or the old pool opts := x509.VerifyOptions{ - Roots: rca.Pool, + Roots: rootCAPool, } - // Check to see if this certificate was signed by our CA, and isn't expired + + // Check to see if this certificate was signed by one of the CAs, and isn't expired if _, err := X509Cert.Verify(opts); err != nil { return nil, err } @@ -211,6 +224,15 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit return nil, err } + // If a CA certificate bundle exists, it has been validated before, so let's write it to disk. + // Root rotation should always happen by appending a new CA cert, and later removing the old one, + // so it's safer to do it in this order of operations + if newRootErr == nil { + if err := newRootCA.saveCertificate(); err != nil { + return nil, err + } + } + if err := kw.Write(signedCert, key, kekUpdate); err != nil { return nil, err } @@ -316,10 +338,28 @@ func (rca *RootCA) AppendFirstRootPEM(cert []byte) ([]byte, error) { return certChain, nil } +func (rca *RootCA) saveCertificate() error { + if rca.Cert == nil { + return errors.New("no valid certificate bundle found") + + } + if rca.Path.Cert == "" { + return errors.New("no path found for this root CA") + } + + // Make sure the necessary dirs exist and they are writable + err := os.MkdirAll(filepath.Dir(rca.Path.Cert), 0755) + if err != nil { + return err + } + + return ioutils.AtomicWriteFile(rca.Path.Cert, rca.Cert, 0644) +} + // NewRootCA creates a new RootCA object from unparsed PEM cert bundle and key byte // slices. key may be nil, and in this case NewRootCA will return a RootCA // without a signer. -func NewRootCA(certBytes, keyBytes []byte, certExpiry time.Duration) (RootCA, error) { +func NewRootCA(certBytes, keyBytes []byte, paths CertPaths, certExpiry time.Duration) (RootCA, error) { // Parse all the certificates in the cert bundle parsedCerts, err := helpers.ParseCertificatesPEM(certBytes) if err != nil { @@ -345,7 +385,7 @@ func NewRootCA(certBytes, keyBytes []byte, certExpiry time.Duration) (RootCA, er if len(keyBytes) == 0 { // This RootCA does not have a valid signer. - return RootCA{Cert: certBytes, Digest: digest, Pool: pool}, nil + return RootCA{Cert: certBytes, Digest: digest, Pool: pool, Path: paths}, nil } var ( @@ -387,7 +427,7 @@ func NewRootCA(certBytes, keyBytes []byte, certExpiry time.Duration) (RootCA, er keyBlock, _ := pem.Decode(keyBytes) if keyBlock == nil { // This RootCA does not have a valid signer. - return RootCA{Cert: certBytes, Digest: digest, Pool: pool}, nil + return RootCA{Cert: certBytes, Digest: digest, Pool: pool, Path: paths}, nil } if passphraseStr != "" && !x509.IsEncryptedPEMBlock(keyBlock) { keyBytes, err = EncryptECPrivateKey(keyBytes, passphraseStr) @@ -396,7 +436,7 @@ func NewRootCA(certBytes, keyBytes []byte, certExpiry time.Duration) (RootCA, er } } - return RootCA{Signer: signer, Key: keyBytes, Digest: digest, Cert: certBytes, Pool: pool}, nil + return RootCA{Signer: signer, Key: keyBytes, Digest: digest, Cert: certBytes, Pool: pool, Path: paths}, nil } func ensureCertKeyMatch(cert *x509.Certificate, key crypto.PublicKey) error { @@ -414,8 +454,7 @@ func ensureCertKeyMatch(cert *x509.Certificate, key crypto.PublicKey) error { return errors.New("certificate key mismatch") } -// GetLocalRootCA validates if the contents of the file are a valid self-signed -// CA certificate, and returns the PEM-encoded Certificate if so +// GetLocalRootCA returns the PEM-encoded root CA Certificate if it exists func GetLocalRootCA(paths CertPaths) (RootCA, error) { // Check if we have a Certificate file cert, err := ioutil.ReadFile(paths.Cert) @@ -427,17 +466,7 @@ func GetLocalRootCA(paths CertPaths) (RootCA, error) { return RootCA{}, err } - key, err := ioutil.ReadFile(paths.Key) - if err != nil { - if !os.IsNotExist(err) { - return RootCA{}, err - } - // There may not be a local key. It's okay to pass in a nil - // key. We'll get a root CA without a signer. - key = nil - } - - return NewRootCA(cert, key, DefaultNodeCertExpiration) + return NewRootCA(cert, nil, paths, DefaultNodeCertExpiration) } func getGRPCConnection(creds credentials.TransportCredentials, r remotes.Remotes) (*grpc.ClientConn, api.Peer, error) { @@ -530,13 +559,13 @@ func CreateRootCA(rootCN string, paths CertPaths) (RootCA, error) { return RootCA{}, err } - rootCA, err := NewRootCA(cert, key, DefaultNodeCertExpiration) + rootCA, err := NewRootCA(cert, key, paths, DefaultNodeCertExpiration) if err != nil { return RootCA{}, err } // save the cert to disk - if err := saveRootCA(rootCA, paths); err != nil { + if err := rootCA.saveCertificate(); err != nil { return RootCA{}, err } @@ -545,9 +574,9 @@ func CreateRootCA(rootCN string, paths CertPaths) (RootCA, error) { // GetRemoteSignedCertificate submits a CSR to a remote CA server address, // and that is part of a CA identified by a specific certificate pool. -func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x509.CertPool, config CertificateRequestConfig) ([]byte, error) { +func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x509.CertPool, config CertificateRequestConfig) ([]byte, []byte, error) { if rootCAPool == nil { - return nil, errors.New("valid root CA pool required") + return nil, nil, errors.New("valid root CA pool required") } creds := config.Credentials @@ -560,7 +589,7 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 conn, peer, err := getGRPCConnection(creds, config.Remotes) if err != nil { - return nil, err + return nil, nil, err } defer conn.Close() @@ -572,7 +601,7 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 issueResponse, err := caClient.IssueNodeCertificate(ctx, issueRequest) if err != nil { config.Remotes.Observe(peer, -remotes.DefaultObservationWeight) - return nil, err + return nil, nil, err } statusRequest := &api.NodeCertificateStatusRequest{NodeID: issueResponse.NodeID} @@ -590,13 +619,13 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 statusResponse, err := caClient.NodeCertificateStatus(ctx, statusRequest) if err != nil { config.Remotes.Observe(peer, -remotes.DefaultObservationWeight) - return nil, err + return nil, nil, err } // If the certificate was issued, return if statusResponse.Status.State == api.IssuanceStateIssued { if statusResponse.Certificate == nil { - return nil, errors.New("no certificate in CertificateStatus response") + return nil, nil, errors.New("no certificate in CertificateStatus response") } // The certificate in the response must match the CSR @@ -606,7 +635,7 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 // current request. if bytes.Equal(statusResponse.Certificate.CSR, csr) { config.Remotes.Observe(peer, remotes.DefaultObservationWeight) - return statusResponse.Certificate.Certificate, nil + return statusResponse.Certificate.Certificate, statusResponse.RootCABundle, nil } } @@ -640,17 +669,6 @@ func readCertValidity(kr KeyReader) (time.Time, time.Time, error) { } -func saveRootCA(rootCA RootCA, paths CertPaths) error { - // Make sure the necessary dirs exist and they are writable - err := os.MkdirAll(filepath.Dir(paths.Cert), 0755) - if err != nil { - return err - } - - // If the root certificate got returned successfully, save the rootCA to disk. - return ioutils.AtomicWriteFile(paths.Cert, rootCA.Cert, 0644) -} - // GenerateNewCSR returns a newly generated key and CSR signed with said key func GenerateNewCSR() (csr, key []byte, err error) { req := &cfcsr.CertificateRequest{ diff --git a/ca/config.go b/ca/config.go index e15454b5b4..41e1e4b64c 100644 --- a/ca/config.go +++ b/ca/config.go @@ -120,8 +120,15 @@ func (s *SecurityConfig) UpdateRootCA(cert, key []byte, certExpiry time.Duration s.mu.Lock() defer s.mu.Unlock() - rootCA, err := NewRootCA(cert, key, certExpiry) + // Create a new RootCA, keeping the path of the old RootCA + rootCA, err := NewRootCA(cert, key, s.rootCA.Path, certExpiry) + if err != nil { + return err + } + // Attempt to write the new certificate to disk + err = rootCA.saveCertificate() if err == nil { + // No errors, swam the current rootCA s.rootCA = &rootCA } @@ -232,7 +239,8 @@ func DownloadRootCA(ctx context.Context, paths CertPaths, token string, r remote } // Save root CA certificate to disk - if err = saveRootCA(rootCA, paths); err != nil { + rootCA.Path = paths + if err = rootCA.saveCertificate(); err != nil { return RootCA{}, err } @@ -454,7 +462,6 @@ func RenewTLSConfig(ctx context.Context, s *SecurityConfig, remotes remotes.Remo // Since the expiration of the certificate is managed remotely we should update our // retry timer on every iteration of this loop. - // Retrieve the current certificate expiration information. validFrom, validUntil, err := readCertValidity(s.KeyReader()) if err != nil { // We failed to read the expiration, let's stick with the starting default diff --git a/ca/server.go b/ca/server.go index fa55d38534..51f78f4512 100644 --- a/ca/server.go +++ b/ca/server.go @@ -142,8 +142,9 @@ func (s *Server) NodeCertificateStatus(ctx context.Context, request *api.NodeCer // If this certificate has a final state, return it immediately (both pending and renew are transition states) if isFinalState(node.Certificate.Status) { return &api.NodeCertificateStatusResponse{ - Status: &node.Certificate.Status, - Certificate: &node.Certificate, + Status: &node.Certificate.Status, + Certificate: &node.Certificate, + RootCABundle: s.securityConfig.RootCA().Cert, }, nil } @@ -164,8 +165,9 @@ func (s *Server) NodeCertificateStatus(ctx context.Context, request *api.NodeCer if isFinalState(v.Node.Certificate.Status) { cert := v.Node.Certificate.Copy() return &api.NodeCertificateStatusResponse{ - Status: &cert.Status, - Certificate: cert, + Status: &cert.Status, + Certificate: cert, + RootCABundle: s.securityConfig.RootCA().Cert, }, nil } } From e2aed4da0e5a53136c610803088eef905c9b2a2e Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Fri, 18 Nov 2016 16:55:12 -0800 Subject: [PATCH 3/4] Fixed tests Signed-off-by: Diogo Monica --- ca/certificates.go | 48 ++++++++------- ca/certificates_test.go | 126 ++++++++++++++++++++-------------------- ca/config.go | 2 +- ca/config_test.go | 6 +- ca/testutils/cautils.go | 2 + node/node_test.go | 7 +-- 6 files changed, 97 insertions(+), 94 deletions(-) diff --git a/ca/certificates.go b/ca/certificates.go index 6c94005228..c0c78e7119 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -109,7 +109,7 @@ type RootCA struct { Digest digest.Digest // This signer will be nil if the node doesn't have the appropriate key material Signer cfsigner.Signer - // Stores the location on disk where the RootCA lives + // Path stores the location on disk where the RootCA lives Path CertPaths } @@ -165,9 +165,9 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit // Get the remote manager to issue a CA signed certificate for this node // Retry up to 5 times in case the manager we first try to contact isn't // responding properly (for example, it may have just been demoted). - var signedCert, rootCABundle []byte + var response *api.NodeCertificateStatusResponse for i := 0; i != 5; i++ { - signedCert, rootCABundle, err = GetRemoteSignedCertificate(ctx, csr, rca.Pool, config) + response, err = GetRemoteSignedCertificate(ctx, csr, rca.Pool, config) if err == nil { break } @@ -179,7 +179,7 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit // Доверяй, но проверяй. // Before we overwrite our local key + certificate, let's make sure the server gave us one that is valid // Create an X509Cert so we can .Verify() - certBlock, _ := pem.Decode(signedCert) + certBlock, _ := pem.Decode(response.Certificate.Certificate) if certBlock == nil { return nil, errors.New("failed to parse certificate PEM") } @@ -189,12 +189,18 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit } // We retrieve the certificate with the current root pool, so we know this was issued by a legitimate manager. // However, there might have been a server-side root rotation, so we verify this cert with a new pool. - // If we got a valid rootCABundle, turn it into a Pool, and verify the newly minted certificate using it. + // If we got a valid response.RootCABundle, turn it into a Pool, and verify the newly minted certificate using it. + var ( + newRootErr error + newRootCA RootCA + ) rootCAPool := rca.Pool - newRootCA, newRootErr := NewRootCA(rootCABundle, nil, rca.Path, time.Minute) - if newRootErr == nil { - // The rootCABundle we got from the remote server seems to be good, use it - rootCAPool = newRootCA.Pool + if response.RootCABundle != nil { + newRootCA, newRootErr = NewRootCA(response.RootCABundle, nil, rca.Path, time.Minute) + if newRootErr == nil { + // The response.RootCABundle we got from the remote server seems to be good, use it + rootCAPool = newRootCA.Pool + } } // Create VerifyOptions with either the new certificate bundle, or the old pool @@ -208,7 +214,7 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit } // Create a valid TLSKeyPair out of the PEM encoded private key and certificate - tlsKeyPair, err := tls.X509KeyPair(signedCert, key) + tlsKeyPair, err := tls.X509KeyPair(response.Certificate.Certificate, key) if err != nil { return nil, err } @@ -224,16 +230,16 @@ func (rca *RootCA) RequestAndSaveNewCertificates(ctx context.Context, kw KeyWrit return nil, err } - // If a CA certificate bundle exists, it has been validated before, so let's write it to disk. + // If a CA certificate bundle exists it has been validated before. If it's different, let's write it to disk. // Root rotation should always happen by appending a new CA cert, and later removing the old one, - // so it's safer to do it in this order of operations - if newRootErr == nil { + // so it's safer to do it in this order of operations (write root, write certificate) + if newRootErr == nil && !bytes.Equal(rca.Cert, response.RootCABundle) { if err := newRootCA.saveCertificate(); err != nil { return nil, err } } - if err := kw.Write(signedCert, key, kekUpdate); err != nil { + if err := kw.Write(response.Certificate.Certificate, key, kekUpdate); err != nil { return nil, err } @@ -574,9 +580,9 @@ func CreateRootCA(rootCN string, paths CertPaths) (RootCA, error) { // GetRemoteSignedCertificate submits a CSR to a remote CA server address, // and that is part of a CA identified by a specific certificate pool. -func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x509.CertPool, config CertificateRequestConfig) ([]byte, []byte, error) { +func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x509.CertPool, config CertificateRequestConfig) (*api.NodeCertificateStatusResponse, error) { if rootCAPool == nil { - return nil, nil, errors.New("valid root CA pool required") + return nil, errors.New("valid root CA pool required") } creds := config.Credentials @@ -589,7 +595,7 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 conn, peer, err := getGRPCConnection(creds, config.Remotes) if err != nil { - return nil, nil, err + return nil, err } defer conn.Close() @@ -601,7 +607,7 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 issueResponse, err := caClient.IssueNodeCertificate(ctx, issueRequest) if err != nil { config.Remotes.Observe(peer, -remotes.DefaultObservationWeight) - return nil, nil, err + return nil, err } statusRequest := &api.NodeCertificateStatusRequest{NodeID: issueResponse.NodeID} @@ -619,13 +625,13 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 statusResponse, err := caClient.NodeCertificateStatus(ctx, statusRequest) if err != nil { config.Remotes.Observe(peer, -remotes.DefaultObservationWeight) - return nil, nil, err + return nil, err } // If the certificate was issued, return if statusResponse.Status.State == api.IssuanceStateIssued { if statusResponse.Certificate == nil { - return nil, nil, errors.New("no certificate in CertificateStatus response") + return nil, errors.New("no certificate in CertificateStatus response") } // The certificate in the response must match the CSR @@ -635,7 +641,7 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 // current request. if bytes.Equal(statusResponse.Certificate.CSR, csr) { config.Remotes.Observe(peer, remotes.DefaultObservationWeight) - return statusResponse.Certificate.Certificate, statusResponse.RootCABundle, nil + return statusResponse, nil } } diff --git a/ca/certificates_test.go b/ca/certificates_test.go index dd235d8e90..65d7214eaf 100644 --- a/ca/certificates_test.go +++ b/ca/certificates_test.go @@ -1,11 +1,7 @@ package ca_test import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "crypto/sha256" - "crypto/x509" "encoding/hex" "encoding/pem" "io/ioutil" @@ -102,27 +98,6 @@ func TestGetLocalRootCA(t *testing.T) { assert.NoError(t, err) assert.Equal(t, rootCA.Cert, rootCA2.Cert) assert.False(t, rootCA2.CanSign()) - - // write private key and assert we can load it and sign - assert.NoError(t, ioutil.WriteFile(paths.RootCA.Key, rootCA.Key, os.FileMode(0600))) - rootCA3, err := ca.GetLocalRootCA(paths.RootCA) - assert.NoError(t, err) - assert.Equal(t, rootCA.Cert, rootCA3.Cert) - assert.True(t, rootCA3.CanSign()) - - // Try with a private key that does not match the CA cert public key. - privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - assert.NoError(t, err) - privKeyBytes, err := x509.MarshalECPrivateKey(privKey) - assert.NoError(t, err) - privKeyPem := pem.EncodeToMemory(&pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: privKeyBytes, - }) - assert.NoError(t, ioutil.WriteFile(paths.RootCA.Key, privKeyPem, os.FileMode(0600))) - - _, err = ca.GetLocalRootCA(paths.RootCA) - assert.EqualError(t, err, "certificate key mismatch") } func TestGetLocalRootCAInvalidCert(t *testing.T) { @@ -141,25 +116,6 @@ some random garbage\n require.Error(t, err) } -func TestGetLocalRootCAInvalidKey(t *testing.T) { - tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") - assert.NoError(t, err) - defer os.RemoveAll(tempBaseDir) - - paths := ca.NewConfigPaths(tempBaseDir) - // Create the local Root CA to ensure that we can reload it correctly. - _, err = ca.CreateRootCA("rootCN", paths.RootCA) - require.NoError(t, err) - - // Write some garbage to the root key - this will cause the loading to fail - require.NoError(t, ioutil.WriteFile(paths.RootCA.Key, []byte(`-----BEGIN EC PRIVATE KEY-----\n -some random garbage\n ------END EC PRIVATE KEY-----`), 0600)) - - _, err = ca.GetLocalRootCA(paths.RootCA) - require.Error(t, err) -} - func TestEncryptECPrivateKey(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) @@ -285,7 +241,7 @@ func TestRequestAndSaveNewCertificates(t *testing.T) { defer tc.Stop() // Copy the current RootCA without the signer - rca := ca.RootCA{Cert: tc.RootCA.Cert, Pool: tc.RootCA.Pool} + rca := ca.RootCA{Cert: tc.RootCA.Cert, Pool: tc.RootCA.Pool, Path: tc.RootCA.Path} cert, err := rca.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, ca.CertificateRequestConfig{ Token: tc.ManagerToken, @@ -342,7 +298,6 @@ func TestRequestAndSaveNewCertificates(t *testing.T) { _, _, err = ca.NewKeyReadWriter(tc.Paths.Node, []byte("kek!"), nil).Read() require.NoError(t, err) - // if it's a worker though, the key is always unencrypted, even though the manager key is encrypted _, err = rca.RequestAndSaveNewCertificates(tc.Context, tc.KeyReadWriter, ca.CertificateRequestConfig{ Token: tc.WorkerToken, @@ -410,16 +365,17 @@ func TestGetRemoteSignedCertificate(t *testing.T) { csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) - certs, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, + response, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, ca.CertificateRequestConfig{ Token: tc.ManagerToken, Remotes: tc.Remotes, }) assert.NoError(t, err) - assert.NotNil(t, certs) + assert.NotNil(t, response.Certificate.Certificate) + assert.NotNil(t, response.RootCABundle) // Test the expiration for a manager certificate - parsedCerts, err := helpers.ParseCertificatesPEM(certs) + parsedCerts, err := helpers.ParseCertificatesPEM(response.Certificate.Certificate) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, -1).Before(parsedCerts[0].NotAfter)) @@ -427,14 +383,15 @@ func TestGetRemoteSignedCertificate(t *testing.T) { assert.Equal(t, parsedCerts[0].Subject.OrganizationalUnit[0], ca.ManagerRole) // Test the expiration for an worker certificate - certs, err = ca.GetRemoteSignedCertificate(tc.Context, csr, tc.RootCA.Pool, + response, err = ca.GetRemoteSignedCertificate(tc.Context, csr, tc.RootCA.Pool, ca.CertificateRequestConfig{ Token: tc.WorkerToken, Remotes: tc.Remotes, }) assert.NoError(t, err) - assert.NotNil(t, certs) - parsedCerts, err = helpers.ParseCertificatesPEM(certs) + assert.NotNil(t, response.Certificate.Certificate) + assert.NotNil(t, response.RootCABundle) + parsedCerts, err = helpers.ParseCertificatesPEM(response.Certificate.Certificate) assert.NoError(t, err) assert.Len(t, parsedCerts, 2) assert.True(t, time.Now().Add(ca.DefaultNodeCertExpiration).AddDate(0, 0, -1).Before(parsedCerts[0].NotAfter)) @@ -450,13 +407,56 @@ func TestGetRemoteSignedCertificateNodeInfo(t *testing.T) { csr, _, err := ca.GenerateNewCSR() assert.NoError(t, err) - cert, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, + response, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, ca.CertificateRequestConfig{ Token: tc.WorkerToken, Remotes: tc.Remotes, }) assert.NoError(t, err) - assert.NotNil(t, cert) + assert.NotNil(t, response.Certificate.Certificate) + assert.NotNil(t, response.RootCABundle) +} + +func TestGetRemoteSignedCertificateWithNewRootCA(t *testing.T) { + tc := testutils.NewTestCA(t) + defer tc.Stop() + + // Create a new CSR to be signed + csr, _, err := ca.GenerateNewCSR() + assert.NoError(t, err) + + response, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, + ca.CertificateRequestConfig{ + Token: tc.WorkerToken, + Remotes: tc.Remotes, + }) + assert.NoError(t, err) + assert.NotNil(t, response.Certificate.Certificate) + assert.NotNil(t, response.RootCABundle) + + // Crete a temporary root CA for the cert + cert, _, err := testutils.CreateRootCertAndKey("rootCNnew") + assert.NoError(t, err) + // Append to a bundle + CABundle := append(tc.RootCA.Cert, cert...) + + assert.NoError(t, tc.MemoryStore.Update(func(tx store.Tx) error { + clusters, err := store.FindClusters(tx, store.ByIDPrefix(tc.Organization)) + assert.NoError(t, err) + clusters[0].RootCA.CACert = CABundle + clusters[0].RootCA.CAKey = tc.RootCA.Key + return store.UpdateCluster(tx, clusters[0]) + })) + + response, err = ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, + ca.CertificateRequestConfig{ + Token: tc.WorkerToken, + Remotes: tc.Remotes, + }) + assert.NoError(t, err) + assert.NotNil(t, response.Certificate.Certificate) + assert.NotNil(t, response.RootCABundle) + assert.Equal(t, response.RootCABundle, CABundle) } func TestGetRemoteSignedCertificateWithPending(t *testing.T) { @@ -507,7 +507,7 @@ func TestNewRootCA(t *testing.T) { rootCA, err := ca.CreateRootCA("rootCN", paths.RootCA) assert.NoError(t, err) - newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, ca.DefaultNodeCertExpiration) + newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, paths.RootCA, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, rootCA, newRootCA) } @@ -532,7 +532,7 @@ func TestNewRootCABundle(t *testing.T) { err = ioutil.WriteFile(paths.RootCA.Cert, bundle, 0644) assert.NoError(t, err) - newRootCA, err := ca.NewRootCA(bundle, firstRootCA.Key, ca.DefaultNodeCertExpiration) + newRootCA, err := ca.NewRootCA(bundle, firstRootCA.Key, paths.RootCA, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, bundle, newRootCA.Cert) assert.Equal(t, 2, len(newRootCA.Pool.Subjects())) @@ -564,7 +564,7 @@ func TestNewRootCANonDefaultExpiry(t *testing.T) { rootCA, err := ca.CreateRootCA("rootCN", paths.RootCA) assert.NoError(t, err) - newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, 1*time.Hour) + newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, paths.RootCA, 1*time.Hour) assert.NoError(t, err) // Create and sign a new CSR @@ -581,7 +581,7 @@ func TestNewRootCANonDefaultExpiry(t *testing.T) { // Sign the same CSR again, this time with a 59 Minute expiration RootCA (under the 60 minute minimum). // This should use the default of 3 months - newRootCA, err = ca.NewRootCA(rootCA.Cert, rootCA.Key, 59*time.Minute) + newRootCA, err = ca.NewRootCA(rootCA.Cert, rootCA.Key, paths.RootCA, 59*time.Minute) assert.NoError(t, err) cert, err = newRootCA.ParseValidateAndSignCSR(csr, "CN", ca.ManagerRole, "ORG") @@ -609,7 +609,7 @@ func TestNewRootCAWithPassphrase(t *testing.T) { // Ensure that we're encrypting the Key bytes out of NewRoot if there // is a passphrase set as an env Var os.Setenv(ca.PassphraseENVVar, "password1") - newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, ca.DefaultNodeCertExpiration) + newRootCA, err := ca.NewRootCA(rootCA.Cert, rootCA.Key, paths.RootCA, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.NotEqual(t, rootCA.Key, newRootCA.Key) assert.Equal(t, rootCA.Cert, newRootCA.Cert) @@ -618,7 +618,7 @@ func TestNewRootCAWithPassphrase(t *testing.T) { // Ensure that we're decrypting the Key bytes out of NewRoot if there // is a passphrase set as an env Var - anotherNewRootCA, err := ca.NewRootCA(newRootCA.Cert, newRootCA.Key, ca.DefaultNodeCertExpiration) + anotherNewRootCA, err := ca.NewRootCA(newRootCA.Cert, newRootCA.Key, paths.RootCA, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, newRootCA, anotherNewRootCA) assert.NotContains(t, string(rootCA.Key), string(anotherNewRootCA.Key)) @@ -627,19 +627,19 @@ func TestNewRootCAWithPassphrase(t *testing.T) { // Ensure that we cant decrypt the Key bytes out of NewRoot if there // is a wrong passphrase set as an env Var os.Setenv(ca.PassphraseENVVar, "password2") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, ca.DefaultNodeCertExpiration) + anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, paths.RootCA, ca.DefaultNodeCertExpiration) assert.Error(t, err) // Ensure that we cant decrypt the Key bytes out of NewRoot if there // is a wrong passphrase set as an env Var os.Setenv(ca.PassphraseENVVarPrev, "password2") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, ca.DefaultNodeCertExpiration) + anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, paths.RootCA, ca.DefaultNodeCertExpiration) assert.Error(t, err) // Ensure that we can decrypt the Key bytes out of NewRoot if there // is a wrong passphrase set as an env Var, but a valid as Prev os.Setenv(ca.PassphraseENVVarPrev, "password1") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, ca.DefaultNodeCertExpiration) + anotherNewRootCA, err = ca.NewRootCA(newRootCA.Cert, newRootCA.Key, paths.RootCA, ca.DefaultNodeCertExpiration) assert.NoError(t, err) assert.Equal(t, newRootCA, anotherNewRootCA) assert.NotContains(t, string(rootCA.Key), string(anotherNewRootCA.Key)) diff --git a/ca/config.go b/ca/config.go index 41e1e4b64c..db5ba02236 100644 --- a/ca/config.go +++ b/ca/config.go @@ -128,7 +128,7 @@ func (s *SecurityConfig) UpdateRootCA(cert, key []byte, certExpiry time.Duration // Attempt to write the new certificate to disk err = rootCA.saveCertificate() if err == nil { - // No errors, swam the current rootCA + // No errors, save the current rootCA s.rootCA = &rootCA } diff --git a/ca/config_test.go b/ca/config_test.go index 6bcde60739..e15c074ebf 100644 --- a/ca/config_test.go +++ b/ca/config_test.go @@ -205,7 +205,7 @@ func TestRenewTLSConfigWorker(t *testing.T) { // Create a new RootCA, and change the policy to issue 6 minute certificates // Because of the default backdate of 5 minutes, this issues certificates // valid for 1 minute. - newRootCA, err := ca.NewRootCA(tc.RootCA.Cert, tc.RootCA.Key, ca.DefaultNodeCertExpiration) + newRootCA, err := ca.NewRootCA(tc.RootCA.Cert, tc.RootCA.Key, tc.Paths.RootCA, ca.DefaultNodeCertExpiration) assert.NoError(t, err) newRootCA.Signer.SetPolicy(&cfconfig.Signing{ Default: &cfconfig.SigningProfile{ @@ -259,7 +259,7 @@ func TestRenewTLSConfigManager(t *testing.T) { // Create a new RootCA, and change the policy to issue 6 minute certificates // Because of the default backdate of 5 minutes, this issues certificates // valid for 1 minute. - newRootCA, err := ca.NewRootCA(tc.RootCA.Cert, tc.RootCA.Key, ca.DefaultNodeCertExpiration) + newRootCA, err := ca.NewRootCA(tc.RootCA.Cert, tc.RootCA.Key, tc.Paths.RootCA, ca.DefaultNodeCertExpiration) assert.NoError(t, err) newRootCA.Signer.SetPolicy(&cfconfig.Signing{ Default: &cfconfig.SigningProfile{ @@ -315,7 +315,7 @@ func TestRenewTLSConfigWithNoNode(t *testing.T) { // Create a new RootCA, and change the policy to issue 6 minute certificates. // Because of the default backdate of 5 minutes, this issues certificates // valid for 1 minute. - newRootCA, err := ca.NewRootCA(tc.RootCA.Cert, tc.RootCA.Key, ca.DefaultNodeCertExpiration) + newRootCA, err := ca.NewRootCA(tc.RootCA.Cert, tc.RootCA.Key, tc.Paths.RootCA, ca.DefaultNodeCertExpiration) assert.NoError(t, err) newRootCA.Signer.SetPolicy(&cfconfig.Signing{ Default: &cfconfig.SigningProfile{ diff --git a/ca/testutils/cautils.go b/ca/testutils/cautils.go index d249825c70..a53f7b6fba 100644 --- a/ca/testutils/cautils.go +++ b/ca/testutils/cautils.go @@ -301,6 +301,7 @@ func genSecurityConfig(s *store.MemoryStore, rootCA ca.RootCA, krw *ca.KeyReadWr Cert: rootCA.Cert, Digest: rootCA.Digest, Pool: rootCA.Pool, + Path: rootCA.Path, } } @@ -398,5 +399,6 @@ func createAndWriteRootCA(rootCN string, paths ca.CertPaths, expiry time.Duratio Cert: cert, Pool: pool, Digest: digest.FromBytes(cert), + Path: paths, }, nil } diff --git a/node/node_test.go b/node/node_test.go index ef74912ef5..10a68f9892 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -148,7 +148,7 @@ func TestLoadSecurityConfigLoadFromDisk(t *testing.T) { } // If there is no CA, and a join addr is provided, one is downloaded from the -// join server. If there is a CA, it is just loaded from disk. The TLS key and +// join server. If there is a CA, it is just loaded from disk. The TLS key and // cert are also downloaded. func TestLoadSecurityConfigDownloadAllCerts(t *testing.T) { tempdir, err := ioutil.TempDir("", "test-join-node") @@ -225,11 +225,6 @@ func TestLoadSecurityConfigDownloadAllCerts(t *testing.T) { _, err = node.loadSecurityConfig(context.Background()) require.NoError(t, err) - // make sure the CA cert has not been replaced - readCertBytes, err := ioutil.ReadFile(paths.RootCA.Cert) - require.NoError(t, err) - require.Equal(t, certBytes, readCertBytes) - // the TLS node cert and key were saved to disk encrypted, though _, _, err = ca.NewKeyReadWriter(paths.Node, nil, nil).Read() require.Error(t, err) From 4bcc6481e882050be17dfb47d1d6289197aa5dc1 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Wed, 23 Nov 2016 18:22:03 -0800 Subject: [PATCH 4/4] Fixed potential crash when server returned nil state and added one more test Signed-off-by: Diogo Monica --- ca/certificates.go | 2 +- ca/certificates_test.go | 51 +++++++++++++++++++++++++++++++++++++++++ ca/testutils/cautils.go | 26 +++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/ca/certificates.go b/ca/certificates.go index c0c78e7119..75b604a69e 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -629,7 +629,7 @@ func GetRemoteSignedCertificate(ctx context.Context, csr []byte, rootCAPool *x50 } // If the certificate was issued, return - if statusResponse.Status.State == api.IssuanceStateIssued { + if statusResponse.Status != nil && statusResponse.Status.State == api.IssuanceStateIssued { if statusResponse.Certificate == nil { return nil, errors.New("no certificate in CertificateStatus response") } diff --git a/ca/certificates_test.go b/ca/certificates_test.go index 65d7214eaf..a91d5dbec5 100644 --- a/ca/certificates_test.go +++ b/ca/certificates_test.go @@ -5,10 +5,13 @@ import ( "encoding/hex" "encoding/pem" "io/ioutil" + "net" "os" "testing" "time" + "google.golang.org/grpc" + cfcsr "github.com/cloudflare/cfssl/csr" "github.com/cloudflare/cfssl/helpers" "github.com/docker/distribution/digest" @@ -17,6 +20,7 @@ import ( "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/manager/state" "github.com/docker/swarmkit/manager/state/store" + "github.com/docker/swarmkit/remotes" "github.com/phayes/permbits" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -459,6 +463,53 @@ func TestGetRemoteSignedCertificateWithNewRootCA(t *testing.T) { assert.Equal(t, response.RootCABundle, CABundle) } +func TestGetRemoteSignedCertificateWithNilCABundle(t *testing.T) { + tc := testutils.NewTestCA(t) + managerConfig, err := tc.NewNodeConfig("swarm-manager") + assert.NoError(t, err) + tc.Stop() + + serverOpts := []grpc.ServerOption{grpc.Creds(managerConfig.ServerTLSCreds)} + grpcServer := grpc.NewServer(serverOpts...) + + // Create a new CSR to be signed + csr, _, err := ca.GenerateNewCSR() + assert.NoError(t, err) + + caServer := &testutils.MockNodeCA{ + NodeID: "NodeID", + NodeMembership: api.NodeMembershipAccepted, + Status: &api.IssuanceStatus{State: api.IssuanceStateIssued}, + Certificate: &api.Certificate{ + CSR: csr, + CN: "NodeID", + Role: api.NodeRoleWorker, + Status: api.IssuanceStatus{ + State: api.IssuanceStateIssued, + }, + Certificate: []byte("CERTIFICATE"), + }, + } + + api.RegisterNodeCAServer(grpcServer, caServer) + l, err := net.Listen("tcp", "127.0.0.1:0") + assert.NoError(t, err) + remotes := remotes.NewRemotes(api.Peer{Addr: l.Addr().String()}) + + go grpcServer.Serve(l) + defer grpcServer.Stop() + defer l.Close() + + response, err := ca.GetRemoteSignedCertificate(context.Background(), csr, tc.RootCA.Pool, + ca.CertificateRequestConfig{ + Token: tc.WorkerToken, + Remotes: remotes, + }) + assert.NoError(t, err) + assert.NotNil(t, response.Certificate.Certificate) + assert.Nil(t, response.RootCABundle) +} + func TestGetRemoteSignedCertificateWithPending(t *testing.T) { t.Parallel() diff --git a/ca/testutils/cautils.go b/ca/testutils/cautils.go index a53f7b6fba..96b3776c39 100644 --- a/ca/testutils/cautils.go +++ b/ca/testutils/cautils.go @@ -402,3 +402,29 @@ func createAndWriteRootCA(rootCN string, paths ca.CertPaths, expiry time.Duratio Path: paths, }, nil } + +// MockNodeCA represents a CA that returns the data we feed directly without processing +type MockNodeCA struct { + NodeID string + NodeMembership api.NodeSpec_Membership + Status *api.IssuanceStatus + Certificate *api.Certificate + RootCABundle []byte +} + +// NodeCertificateStatus fakes the node status endpoint +func (s *MockNodeCA) NodeCertificateStatus(ctx context.Context, request *api.NodeCertificateStatusRequest) (*api.NodeCertificateStatusResponse, error) { + return &api.NodeCertificateStatusResponse{ + Status: s.Status, + Certificate: s.Certificate, + RootCABundle: s.RootCABundle, + }, nil +} + +// IssueNodeCertificate fakes the node Certificate issuance endpoint +func (s *MockNodeCA) IssueNodeCertificate(ctx context.Context, request *api.IssueNodeCertificateRequest) (*api.IssueNodeCertificateResponse, error) { + return &api.IssueNodeCertificateResponse{ + NodeID: s.NodeID, + NodeMembership: s.NodeMembership, + }, nil +}