From 5546c844fecf390880134f2eec407e9ee553b4aa Mon Sep 17 00:00:00 2001 From: cyli Date: Thu, 23 Mar 2017 11:46:30 -0700 Subject: [PATCH 1/3] Add the desired certificate and signing key to the cluster spec. The cluster RootCA object will reflect the current status of the actual cluster RootCA, and the goal is to converge towards the desired CA cert and key. Signed-off-by: cyli --- api/types.pb.go | 694 +++++++++++++++++++++++++++++------------------- api/types.proto | 21 +- 2 files changed, 447 insertions(+), 268 deletions(-) diff --git a/api/types.pb.go b/api/types.pb.go index c72328eac0..26e400761c 100644 --- a/api/types.pb.go +++ b/api/types.pb.go @@ -1345,6 +1345,18 @@ type CAConfig struct { // ExternalCAs is a list of CAs to which a manager node will make // certificate signing requests for node certificates. ExternalCAs []*ExternalCA `protobuf:"bytes,2,rep,name=external_cas,json=externalCas" json:"external_cas,omitempty"` + // SigningCACert is the desired CA certificate to be used as the root and + // signing CA for the swarm. If not provided, indicates that we are either happy + // with the current configuration, or (together with a bump in the ForceRotate value) + // that we want a certificate and key generated for us. + SigningCACert []byte `protobuf:"bytes,3,opt,name=signing_ca_cert,json=signingCaCert,proto3" json:"signing_ca_cert,omitempty"` + // SigningCAKey is the desired private key, matching the signing CA cert, to be used + // to sign certificates for the swarm + SigningCAKey []byte `protobuf:"bytes,4,opt,name=signing_ca_key,json=signingCaKey,proto3" json:"signing_ca_key,omitempty"` + // ForceRotate is a counter that triggers a root CA rotation even if no relevant + // parameters have been in the spec. This will force the manager to generate a new + // certificate and key, if none have been provided. + ForceRotate uint64 `protobuf:"varint,5,opt,name=force_rotate,json=forceRotate,proto3" json:"force_rotate,omitempty"` } func (m *CAConfig) Reset() { *m = CAConfig{} } @@ -1558,6 +1570,9 @@ type RootCA struct { // RootRotation contains the new root cert and key we want to rotate to - if this is nil, we are not in the // middle of a root rotation RootRotation *RootRotation `protobuf:"bytes,5,opt,name=root_rotation,json=rootRotation" json:"root_rotation,omitempty"` + // LastForcedRotation matches the Cluster Spec's CAConfig's ForceRotation counter. + // It indicates when the current CA cert and key were generated (or updated). + LastForcedRotation uint64 `protobuf:"varint,6,opt,name=last_forced_rotation,json=lastForcedRotation,proto3" json:"last_forced_rotation,omitempty"` } func (m *RootCA) Reset() { *m = RootCA{} } @@ -4369,6 +4384,23 @@ func (m *CAConfig) MarshalTo(dAtA []byte) (int, error) { i += n } } + if len(m.SigningCACert) > 0 { + dAtA[i] = 0x1a + i++ + i = encodeVarintTypes(dAtA, i, uint64(len(m.SigningCACert))) + i += copy(dAtA[i:], m.SigningCACert) + } + if len(m.SigningCAKey) > 0 { + dAtA[i] = 0x22 + i++ + i = encodeVarintTypes(dAtA, i, uint64(len(m.SigningCAKey))) + i += copy(dAtA[i:], m.SigningCAKey) + } + if m.ForceRotate != 0 { + dAtA[i] = 0x28 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.ForceRotate)) + } return i, nil } @@ -4711,6 +4743,11 @@ func (m *RootCA) MarshalTo(dAtA []byte) (int, error) { } i += n29 } + if m.LastForcedRotation != 0 { + dAtA[i] = 0x30 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.LastForcedRotation)) + } return i, nil } @@ -5755,6 +5792,17 @@ func (m *CAConfig) Size() (n int) { n += 1 + l + sovTypes(uint64(l)) } } + l = len(m.SigningCACert) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.SigningCAKey) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.ForceRotate != 0 { + n += 1 + sovTypes(uint64(m.ForceRotate)) + } return n } @@ -5898,6 +5946,9 @@ func (m *RootCA) Size() (n int) { l = m.RootRotation.Size() n += 1 + l + sovTypes(uint64(l)) } + if m.LastForcedRotation != 0 { + n += 1 + sovTypes(uint64(m.LastForcedRotation)) + } return n } @@ -6576,6 +6627,9 @@ func (this *CAConfig) String() string { s := strings.Join([]string{`&CAConfig{`, `NodeCertExpiry:` + strings.Replace(fmt.Sprintf("%v", this.NodeCertExpiry), "Duration", "google_protobuf1.Duration", 1) + `,`, `ExternalCAs:` + strings.Replace(fmt.Sprintf("%v", this.ExternalCAs), "ExternalCA", "ExternalCA", 1) + `,`, + `SigningCACert:` + fmt.Sprintf("%v", this.SigningCACert) + `,`, + `SigningCAKey:` + fmt.Sprintf("%v", this.SigningCAKey) + `,`, + `ForceRotate:` + fmt.Sprintf("%v", this.ForceRotate) + `,`, `}`, }, "") return s @@ -6696,6 +6750,7 @@ func (this *RootCA) String() string { `CACertHash:` + fmt.Sprintf("%v", this.CACertHash) + `,`, `JoinTokens:` + strings.Replace(strings.Replace(this.JoinTokens.String(), "JoinTokens", "JoinTokens", 1), `&`, ``, 1) + `,`, `RootRotation:` + strings.Replace(fmt.Sprintf("%v", this.RootRotation), "RootRotation", "RootRotation", 1) + `,`, + `LastForcedRotation:` + fmt.Sprintf("%v", this.LastForcedRotation) + `,`, `}`, }, "") return s @@ -11761,6 +11816,87 @@ func (m *CAConfig) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SigningCACert", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SigningCACert = append(m.SigningCACert[:0], dAtA[iNdEx:postIndex]...) + if m.SigningCACert == nil { + m.SigningCACert = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SigningCAKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SigningCAKey = append(m.SigningCAKey[:0], dAtA[iNdEx:postIndex]...) + if m.SigningCAKey == nil { + m.SigningCAKey = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ForceRotate", wireType) + } + m.ForceRotate = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ForceRotate |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -12794,6 +12930,25 @@ func (m *RootCA) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LastForcedRotation", wireType) + } + m.LastForcedRotation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LastForcedRotation |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -14248,271 +14403,276 @@ var ( func init() { proto.RegisterFile("types.proto", fileDescriptorTypes) } var fileDescriptorTypes = []byte{ - // 4248 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x5a, 0x4d, 0x6c, 0x24, 0x49, - 0x56, 0x76, 0xfd, 0xba, 0xea, 0x55, 0xd9, 0x9d, 0x1d, 0xdd, 0xdb, 0xeb, 0xae, 0xed, 0xb1, 0x6b, - 0x72, 0xa6, 0x77, 0x7e, 0x76, 0x54, 0xd3, 0xed, 0xde, 0x59, 0xf5, 0xcc, 0xb0, 0x33, 0x93, 0xf5, - 0xe3, 0x76, 0x6d, 0xdb, 0x55, 0xa5, 0xa8, 0x72, 0xf7, 0xce, 0x01, 0x52, 0xe1, 0xcc, 0x70, 0x39, - 0xc7, 0x59, 0x19, 0x45, 0x66, 0x96, 0xdd, 0x05, 0x42, 0xb4, 0x38, 0x00, 0xf2, 0x05, 0x8e, 0x48, - 0xc8, 0xa7, 0xe5, 0xc4, 0x81, 0x0b, 0x07, 0x24, 0x2e, 0xcc, 0x81, 0xc3, 0xdc, 0x58, 0x40, 0x42, - 0x2b, 0x90, 0x1a, 0xc6, 0x48, 0xdc, 0x10, 0x5c, 0x56, 0x5c, 0x40, 0x42, 0xf1, 0x93, 0x59, 0x69, - 0x77, 0xd9, 0xee, 0x61, 0xb8, 0xd8, 0x19, 0x2f, 0xbe, 0xf7, 0xe2, 0x45, 0xc4, 0x8b, 0x88, 0xef, - 0x45, 0x14, 0x94, 0xc2, 0xe9, 0x98, 0x06, 0xb5, 0xb1, 0xcf, 0x42, 0x86, 0x90, 0xcd, 0xac, 0x03, - 0xea, 0xd7, 0x82, 0x23, 0xe2, 0x8f, 0x0e, 0x9c, 0xb0, 0x76, 0x78, 0xbf, 0xb2, 0x36, 0x64, 0x6c, - 0xe8, 0xd2, 0xf7, 0x05, 0x62, 0x77, 0xb2, 0xf7, 0x7e, 0xe8, 0x8c, 0x68, 0x10, 0x92, 0xd1, 0x58, - 0x2a, 0x55, 0x56, 0xcf, 0x03, 0xec, 0x89, 0x4f, 0x42, 0x87, 0x79, 0xaa, 0xfe, 0xe6, 0x90, 0x0d, - 0x99, 0xf8, 0x7c, 0x9f, 0x7f, 0x49, 0xa9, 0xbe, 0x06, 0x8b, 0x4f, 0xa8, 0x1f, 0x38, 0xcc, 0x43, - 0x37, 0x21, 0xe7, 0x78, 0x36, 0x7d, 0xb6, 0x92, 0xaa, 0xa6, 0xde, 0xce, 0x62, 0x59, 0xd0, 0xef, - 0x01, 0xb4, 0xf9, 0x47, 0xcb, 0x0b, 0xfd, 0x29, 0xd2, 0x20, 0x73, 0x40, 0xa7, 0x02, 0x51, 0xc4, - 0xfc, 0x93, 0x4b, 0x0e, 0x89, 0xbb, 0x92, 0x96, 0x92, 0x43, 0xe2, 0xea, 0x5f, 0xa7, 0xa0, 0x64, - 0x78, 0x1e, 0x0b, 0x45, 0xeb, 0x01, 0x42, 0x90, 0xf5, 0xc8, 0x88, 0x2a, 0x25, 0xf1, 0x8d, 0x1a, - 0x90, 0x77, 0xc9, 0x2e, 0x75, 0x83, 0x95, 0x74, 0x35, 0xf3, 0x76, 0x69, 0xfd, 0x07, 0xb5, 0x97, - 0xbb, 0x5c, 0x4b, 0x18, 0xa9, 0x6d, 0x09, 0xb4, 0x70, 0x02, 0x2b, 0x55, 0xf4, 0x09, 0x2c, 0x3a, - 0x9e, 0xed, 0x58, 0x34, 0x58, 0xc9, 0x0a, 0x2b, 0xab, 0xf3, 0xac, 0xcc, 0xbc, 0xaf, 0x67, 0xbf, - 0x7a, 0xb1, 0xb6, 0x80, 0x23, 0xa5, 0xca, 0x87, 0x50, 0x4a, 0x98, 0x9d, 0xd3, 0xb7, 0x9b, 0x90, - 0x3b, 0x24, 0xee, 0x84, 0xaa, 0xde, 0xc9, 0xc2, 0x47, 0xe9, 0x87, 0x29, 0xfd, 0x73, 0x28, 0x62, - 0x1a, 0xb0, 0x89, 0x6f, 0xd1, 0x00, 0xbd, 0x03, 0x45, 0x8f, 0x78, 0xcc, 0xb4, 0xc6, 0x93, 0x40, - 0xa8, 0x67, 0xea, 0xe5, 0xd3, 0x17, 0x6b, 0x85, 0x0e, 0xf1, 0x58, 0xa3, 0xb7, 0x13, 0xe0, 0x02, - 0xaf, 0x6e, 0x8c, 0x27, 0x01, 0x7a, 0x1d, 0xca, 0x23, 0x3a, 0x62, 0xfe, 0xd4, 0xdc, 0x9d, 0x86, - 0x34, 0x10, 0x86, 0x33, 0xb8, 0x24, 0x65, 0x75, 0x2e, 0xd2, 0xff, 0x30, 0x05, 0x37, 0x23, 0xdb, - 0x98, 0xfe, 0xfa, 0xc4, 0xf1, 0xe9, 0x88, 0x7a, 0x61, 0x80, 0x3e, 0x80, 0xbc, 0xeb, 0x8c, 0x9c, - 0x50, 0xb6, 0x51, 0x5a, 0x7f, 0x6d, 0x5e, 0x6f, 0x63, 0xaf, 0xb0, 0x02, 0x23, 0x03, 0xca, 0x3e, - 0x0d, 0xa8, 0x7f, 0x28, 0x47, 0x52, 0x34, 0x79, 0xa5, 0xf2, 0x19, 0x15, 0x7d, 0x03, 0x0a, 0x3d, - 0x97, 0x84, 0x7b, 0xcc, 0x1f, 0x21, 0x1d, 0xca, 0xc4, 0xb7, 0xf6, 0x9d, 0x90, 0x5a, 0xe1, 0xc4, - 0x8f, 0x66, 0xf5, 0x8c, 0x0c, 0xdd, 0x82, 0x34, 0x93, 0x0d, 0x15, 0xeb, 0xf9, 0xd3, 0x17, 0x6b, - 0xe9, 0x6e, 0x1f, 0xa7, 0x59, 0xa0, 0x7f, 0x0c, 0xd7, 0x7b, 0xee, 0x64, 0xe8, 0x78, 0x4d, 0x1a, - 0x58, 0xbe, 0x33, 0xe6, 0xd6, 0x79, 0x78, 0xf0, 0xd8, 0x8f, 0xc2, 0x83, 0x7f, 0xc7, 0x21, 0x93, - 0x9e, 0x85, 0x8c, 0xfe, 0x7b, 0x69, 0xb8, 0xde, 0xf2, 0x86, 0x8e, 0x47, 0x93, 0xda, 0x77, 0x61, - 0x99, 0x0a, 0xa1, 0x79, 0x28, 0xc3, 0x58, 0xd9, 0x59, 0x92, 0xd2, 0x28, 0xb6, 0xdb, 0xe7, 0xe2, - 0xed, 0xfe, 0xbc, 0xee, 0xbf, 0x64, 0x7d, 0x6e, 0xd4, 0xb5, 0x60, 0x71, 0x2c, 0x3a, 0x11, 0xac, - 0x64, 0x84, 0xad, 0xbb, 0xf3, 0x6c, 0xbd, 0xd4, 0xcf, 0x28, 0xf8, 0x94, 0xee, 0xb7, 0x09, 0xbe, - 0x7f, 0x4d, 0xc1, 0xb5, 0x0e, 0xb3, 0xcf, 0x8c, 0x43, 0x05, 0x0a, 0xfb, 0x2c, 0x08, 0x13, 0x0b, - 0x2d, 0x2e, 0xa3, 0x87, 0x50, 0x18, 0xab, 0xe9, 0x53, 0xb3, 0x7f, 0x67, 0xbe, 0xcb, 0x12, 0x83, - 0x63, 0x34, 0xfa, 0x18, 0x8a, 0x7e, 0x14, 0x13, 0x2b, 0x99, 0x57, 0x09, 0x9c, 0x19, 0x1e, 0xfd, - 0x18, 0xf2, 0x72, 0x12, 0x56, 0xb2, 0x42, 0xf3, 0xee, 0x2b, 0x8d, 0x39, 0x56, 0x4a, 0xfa, 0x2f, - 0x52, 0xa0, 0x61, 0xb2, 0x17, 0x6e, 0xd3, 0xd1, 0x2e, 0xf5, 0xfb, 0x21, 0x09, 0x27, 0x01, 0xba, - 0x05, 0x79, 0x97, 0x12, 0x9b, 0xfa, 0xa2, 0x93, 0x05, 0xac, 0x4a, 0x68, 0x87, 0x07, 0x39, 0xb1, - 0xf6, 0xc9, 0xae, 0xe3, 0x3a, 0xe1, 0x54, 0x74, 0x73, 0x79, 0xfe, 0x2c, 0x9f, 0xb7, 0x59, 0xc3, - 0x09, 0x45, 0x7c, 0xc6, 0x0c, 0x5a, 0x81, 0xc5, 0x11, 0x0d, 0x02, 0x32, 0xa4, 0xa2, 0xf7, 0x45, - 0x1c, 0x15, 0xf5, 0x8f, 0xa1, 0x9c, 0xd4, 0x43, 0x25, 0x58, 0xdc, 0xe9, 0x3c, 0xee, 0x74, 0x9f, - 0x76, 0xb4, 0x05, 0x74, 0x0d, 0x4a, 0x3b, 0x1d, 0xdc, 0x32, 0x1a, 0x9b, 0x46, 0x7d, 0xab, 0xa5, - 0xa5, 0xd0, 0x12, 0x14, 0x67, 0xc5, 0xb4, 0xfe, 0xe7, 0x29, 0x00, 0x3e, 0x81, 0xaa, 0x53, 0x1f, - 0x41, 0x2e, 0x08, 0x49, 0x28, 0x27, 0x6e, 0x79, 0xfd, 0xcd, 0x79, 0x5e, 0xcf, 0xe0, 0x35, 0xfe, - 0x8f, 0x62, 0xa9, 0x92, 0xf4, 0x30, 0x7d, 0xc6, 0x43, 0xbe, 0x86, 0x88, 0x6d, 0xfb, 0xca, 0x71, - 0xf1, 0xad, 0x7f, 0x0c, 0x39, 0xa1, 0x7d, 0xd6, 0xdd, 0x02, 0x64, 0x9b, 0xfc, 0x2b, 0x85, 0x8a, - 0x90, 0xc3, 0x2d, 0xa3, 0xf9, 0xb9, 0x96, 0x46, 0x1a, 0x94, 0x9b, 0xed, 0x7e, 0xa3, 0xdb, 0xe9, - 0xb4, 0x1a, 0x83, 0x56, 0x53, 0xcb, 0xe8, 0x77, 0x21, 0xd7, 0x1e, 0x71, 0xcb, 0x77, 0x78, 0x54, - 0xec, 0x51, 0x9f, 0x7a, 0x56, 0x14, 0x6c, 0x33, 0x81, 0xfe, 0xf3, 0x22, 0xe4, 0xb6, 0xd9, 0xc4, - 0x0b, 0xd1, 0x7a, 0x62, 0x65, 0x2f, 0xcf, 0xdf, 0x9c, 0x05, 0xb0, 0x36, 0x98, 0x8e, 0xa9, 0x5a, - 0xf9, 0xb7, 0x20, 0x2f, 0xe3, 0x47, 0x75, 0x47, 0x95, 0xb8, 0x3c, 0x24, 0xfe, 0x90, 0x86, 0xaa, - 0x3f, 0xaa, 0x84, 0xde, 0x86, 0x82, 0x4f, 0x89, 0xcd, 0x3c, 0x77, 0x2a, 0xc2, 0xac, 0x20, 0xb7, - 0x5e, 0x4c, 0x89, 0xdd, 0xf5, 0xdc, 0x29, 0x8e, 0x6b, 0xd1, 0x26, 0x94, 0x77, 0x1d, 0xcf, 0x36, - 0xd9, 0x58, 0xee, 0x83, 0xb9, 0x8b, 0x83, 0x52, 0x7a, 0x55, 0x77, 0x3c, 0xbb, 0x2b, 0xc1, 0xb8, - 0xb4, 0x3b, 0x2b, 0xa0, 0x0e, 0x2c, 0x1f, 0x32, 0x77, 0x32, 0xa2, 0xb1, 0xad, 0xbc, 0xb0, 0xf5, - 0xd6, 0xc5, 0xb6, 0x9e, 0x08, 0x7c, 0x64, 0x6d, 0xe9, 0x30, 0x59, 0x44, 0x8f, 0x61, 0x29, 0x1c, - 0x8d, 0xf7, 0x82, 0xd8, 0xdc, 0xa2, 0x30, 0xf7, 0xfd, 0x4b, 0x06, 0x8c, 0xc3, 0x23, 0x6b, 0xe5, - 0x30, 0x51, 0xaa, 0xfc, 0x4e, 0x06, 0x4a, 0x09, 0xcf, 0x51, 0x1f, 0x4a, 0x63, 0x9f, 0x8d, 0xc9, - 0x50, 0xec, 0xe5, 0x6a, 0x2e, 0xee, 0xbf, 0x52, 0xaf, 0x6b, 0xbd, 0x99, 0x22, 0x4e, 0x5a, 0xd1, - 0x4f, 0xd2, 0x50, 0x4a, 0x54, 0xa2, 0x77, 0xa1, 0x80, 0x7b, 0xb8, 0xfd, 0xc4, 0x18, 0xb4, 0xb4, - 0x85, 0xca, 0x9d, 0xe3, 0x93, 0xea, 0x8a, 0xb0, 0x96, 0x34, 0xd0, 0xf3, 0x9d, 0x43, 0x1e, 0x7a, - 0x6f, 0xc3, 0x62, 0x04, 0x4d, 0x55, 0xbe, 0x77, 0x7c, 0x52, 0xfd, 0xee, 0x79, 0x68, 0x02, 0x89, - 0xfb, 0x9b, 0x06, 0x6e, 0x35, 0xb5, 0xf4, 0x7c, 0x24, 0xee, 0xef, 0x13, 0x9f, 0xda, 0xe8, 0xfb, - 0x90, 0x57, 0xc0, 0x4c, 0xa5, 0x72, 0x7c, 0x52, 0xbd, 0x75, 0x1e, 0x38, 0xc3, 0xe1, 0xfe, 0x96, - 0xf1, 0xa4, 0xa5, 0x65, 0xe7, 0xe3, 0x70, 0xdf, 0x25, 0x87, 0x14, 0xbd, 0x09, 0x39, 0x09, 0xcb, - 0x55, 0x6e, 0x1f, 0x9f, 0x54, 0xbf, 0xf3, 0x92, 0x39, 0x8e, 0xaa, 0xac, 0xfc, 0xfe, 0xcf, 0x56, - 0x17, 0xfe, 0xf2, 0x4f, 0x56, 0xb5, 0xf3, 0xd5, 0x95, 0xff, 0x4e, 0xc1, 0xd2, 0x99, 0x29, 0x47, - 0x3a, 0xe4, 0x3d, 0x66, 0xb1, 0xb1, 0xdc, 0xe2, 0x0b, 0x75, 0x38, 0x7d, 0xb1, 0x96, 0xef, 0xb0, - 0x06, 0x1b, 0x4f, 0xb1, 0xaa, 0x41, 0x8f, 0xcf, 0x1d, 0x52, 0x0f, 0x5e, 0x31, 0x9e, 0xe6, 0x1e, - 0x53, 0x9f, 0xc2, 0x92, 0xed, 0x3b, 0x87, 0xd4, 0x37, 0x2d, 0xe6, 0xed, 0x39, 0x43, 0xb5, 0x7d, - 0x57, 0xe6, 0xd9, 0x6c, 0x0a, 0x20, 0x2e, 0x4b, 0x85, 0x86, 0xc0, 0x7f, 0x8b, 0x03, 0xaa, 0xf2, - 0x04, 0xca, 0xc9, 0x08, 0x45, 0xaf, 0x01, 0x04, 0xce, 0x6f, 0x50, 0xc5, 0x79, 0x04, 0x43, 0xc2, - 0x45, 0x2e, 0x11, 0x8c, 0x07, 0xbd, 0x05, 0xd9, 0x11, 0xb3, 0xa5, 0x9d, 0xa5, 0xfa, 0x0d, 0x7e, - 0x4e, 0xfe, 0xe3, 0x8b, 0xb5, 0x12, 0x0b, 0x6a, 0x1b, 0x8e, 0x4b, 0xb7, 0x99, 0x4d, 0xb1, 0x00, - 0xe8, 0x87, 0x90, 0xe5, 0x5b, 0x05, 0xfa, 0x1e, 0x64, 0xeb, 0xed, 0x4e, 0x53, 0x5b, 0xa8, 0x5c, - 0x3f, 0x3e, 0xa9, 0x2e, 0x89, 0x21, 0xe1, 0x15, 0x3c, 0x76, 0xd1, 0x1a, 0xe4, 0x9f, 0x74, 0xb7, - 0x76, 0xb6, 0x79, 0x78, 0xdd, 0x38, 0x3e, 0xa9, 0x5e, 0x8b, 0xab, 0xe5, 0xa0, 0xa1, 0xd7, 0x20, - 0x37, 0xd8, 0xee, 0x6d, 0xf4, 0xb5, 0x74, 0x05, 0x1d, 0x9f, 0x54, 0x97, 0xe3, 0x7a, 0xe1, 0x73, - 0xe5, 0xba, 0x9a, 0xd5, 0x62, 0x2c, 0xd7, 0x7f, 0x99, 0x86, 0x25, 0xcc, 0xc9, 0xb6, 0x1f, 0xf6, - 0x98, 0xeb, 0x58, 0x53, 0xd4, 0x83, 0xa2, 0xc5, 0x3c, 0xdb, 0x49, 0xac, 0xa9, 0xf5, 0x0b, 0x0e, - 0xc6, 0x99, 0x56, 0x54, 0x6a, 0x44, 0x9a, 0x78, 0x66, 0x04, 0xbd, 0x0f, 0x39, 0x9b, 0xba, 0x64, - 0xaa, 0x4e, 0xe8, 0xdb, 0x35, 0x49, 0xe7, 0x6b, 0x11, 0x9d, 0xaf, 0x35, 0x15, 0x9d, 0xc7, 0x12, - 0x27, 0xa8, 0x24, 0x79, 0x66, 0x92, 0x30, 0xa4, 0xa3, 0x71, 0x28, 0x8f, 0xe7, 0x2c, 0x2e, 0x8d, - 0xc8, 0x33, 0x43, 0x89, 0xd0, 0x7d, 0xc8, 0x1f, 0x39, 0x9e, 0xcd, 0x8e, 0xd4, 0x09, 0x7c, 0x89, - 0x51, 0x05, 0xd4, 0x8f, 0xf9, 0xa9, 0x7b, 0xce, 0x4d, 0x3e, 0xde, 0x9d, 0x6e, 0xa7, 0x15, 0x8d, - 0xb7, 0xaa, 0xef, 0x7a, 0x1d, 0xe6, 0xf1, 0xb5, 0x02, 0xdd, 0x8e, 0xb9, 0x61, 0xb4, 0xb7, 0x76, - 0x30, 0x1f, 0xf3, 0x9b, 0xc7, 0x27, 0x55, 0x2d, 0x86, 0x6c, 0x10, 0xc7, 0xe5, 0x94, 0xf0, 0x36, - 0x64, 0x8c, 0xce, 0xe7, 0x5a, 0xba, 0xa2, 0x1d, 0x9f, 0x54, 0xcb, 0x71, 0xb5, 0xe1, 0x4d, 0x67, - 0xcb, 0xe8, 0x7c, 0xbb, 0xfa, 0xdf, 0x64, 0xa0, 0xbc, 0x33, 0xb6, 0x49, 0x48, 0x65, 0x4c, 0xa2, - 0x2a, 0x94, 0xc6, 0xc4, 0x27, 0xae, 0x4b, 0x5d, 0x27, 0x18, 0xa9, 0x44, 0x25, 0x29, 0x42, 0x1f, - 0xbe, 0xea, 0x30, 0xd6, 0x0b, 0x3c, 0xce, 0xfe, 0xe8, 0x9f, 0xd7, 0x52, 0xd1, 0x80, 0xee, 0xc0, - 0xf2, 0x9e, 0xf4, 0xd6, 0x24, 0x96, 0x98, 0xd8, 0x8c, 0x98, 0xd8, 0xda, 0xbc, 0x89, 0x4d, 0xba, - 0x55, 0x53, 0x9d, 0x34, 0x84, 0x16, 0x5e, 0xda, 0x4b, 0x16, 0xd1, 0x03, 0x58, 0x1c, 0x31, 0xcf, - 0x09, 0x99, 0x7f, 0xf5, 0x2c, 0x44, 0x48, 0xf4, 0x2e, 0x5c, 0xe7, 0x93, 0x1b, 0xf9, 0x23, 0xaa, - 0xc5, 0x89, 0x95, 0xc6, 0xd7, 0x46, 0xe4, 0x99, 0x6a, 0x10, 0x73, 0x31, 0xaa, 0x43, 0x8e, 0xf9, - 0x9c, 0x12, 0xe5, 0x85, 0xbb, 0xef, 0x5d, 0xe9, 0xae, 0x2c, 0x74, 0xb9, 0x0e, 0x96, 0xaa, 0xfa, - 0x8f, 0x60, 0xe9, 0x4c, 0x27, 0x38, 0x13, 0xe8, 0x19, 0x3b, 0xfd, 0x96, 0xb6, 0x80, 0xca, 0x50, - 0x68, 0x74, 0x3b, 0x83, 0x76, 0x67, 0x87, 0x53, 0x99, 0x32, 0x14, 0x70, 0x77, 0x6b, 0xab, 0x6e, - 0x34, 0x1e, 0x6b, 0x69, 0xbd, 0x06, 0xa5, 0x84, 0x35, 0xb4, 0x0c, 0xd0, 0x1f, 0x74, 0x7b, 0xe6, - 0x46, 0x1b, 0xf7, 0x07, 0x92, 0x08, 0xf5, 0x07, 0x06, 0x1e, 0x28, 0x41, 0x4a, 0xff, 0x8f, 0x74, - 0x34, 0xa3, 0x8a, 0xfb, 0xd4, 0xcf, 0x72, 0x9f, 0x4b, 0x9c, 0x57, 0xec, 0x67, 0x56, 0x88, 0x39, - 0xd0, 0x87, 0x00, 0x22, 0x70, 0xa8, 0x6d, 0x92, 0x50, 0x4d, 0x7c, 0xe5, 0xa5, 0x41, 0x1e, 0x44, - 0xf9, 0x32, 0x2e, 0x2a, 0xb4, 0x11, 0xa2, 0x1f, 0x43, 0xd9, 0x62, 0xa3, 0xb1, 0x4b, 0x95, 0x72, - 0xe6, 0x4a, 0xe5, 0x52, 0x8c, 0x37, 0xc2, 0x24, 0xfb, 0xca, 0x9e, 0xe5, 0x87, 0xbf, 0x9b, 0x8a, - 0x46, 0x66, 0x0e, 0xe1, 0x2a, 0x43, 0x61, 0xa7, 0xd7, 0x34, 0x06, 0xed, 0xce, 0x23, 0x2d, 0x85, - 0x00, 0xf2, 0x62, 0xa8, 0x9b, 0x5a, 0x9a, 0x13, 0xc5, 0x46, 0x77, 0xbb, 0xb7, 0xd5, 0x12, 0x94, - 0x0b, 0xdd, 0x04, 0x2d, 0x1a, 0x6c, 0x53, 0x0c, 0x64, 0xab, 0xa9, 0x65, 0xd1, 0x0d, 0xb8, 0x16, - 0x4b, 0x95, 0x66, 0x0e, 0xdd, 0x02, 0x14, 0x0b, 0x67, 0x26, 0xf2, 0xfa, 0x6f, 0xc1, 0xb5, 0x06, - 0xf3, 0x42, 0xe2, 0x78, 0x31, 0x89, 0x5e, 0xe7, 0x9d, 0x56, 0x22, 0xd3, 0xb1, 0xe5, 0x9e, 0x5e, - 0xbf, 0x76, 0xfa, 0x62, 0xad, 0x14, 0x43, 0xdb, 0x4d, 0xde, 0xd3, 0xa8, 0x60, 0xf3, 0xf5, 0x3b, - 0x76, 0x6c, 0x31, 0xb8, 0xb9, 0xfa, 0xe2, 0xe9, 0x8b, 0xb5, 0x4c, 0xaf, 0xdd, 0xc4, 0x5c, 0x86, - 0xbe, 0x07, 0x45, 0xfa, 0xcc, 0x09, 0x4d, 0x8b, 0xef, 0xe1, 0x7c, 0x00, 0x73, 0xb8, 0xc0, 0x05, - 0x0d, 0xbe, 0x65, 0xd7, 0x01, 0x7a, 0xcc, 0x0f, 0x55, 0xcb, 0x3f, 0x84, 0xdc, 0x98, 0xf9, 0x22, - 0x83, 0xbd, 0x30, 0x5f, 0xe7, 0x70, 0x19, 0xa8, 0x58, 0x82, 0xf5, 0xbf, 0x4a, 0x03, 0x0c, 0x48, - 0x70, 0xa0, 0x8c, 0x3c, 0x84, 0x62, 0x7c, 0xf7, 0xa1, 0x52, 0xe1, 0x4b, 0x67, 0x3b, 0x06, 0xa3, - 0x07, 0x51, 0xb0, 0xc9, 0xf4, 0x60, 0x6e, 0x2a, 0x13, 0x35, 0x34, 0x8f, 0x61, 0x9f, 0xcd, 0x01, - 0xf8, 0x91, 0x48, 0x7d, 0x5f, 0xcd, 0x3c, 0xff, 0x44, 0x0d, 0x71, 0x2c, 0xc8, 0x41, 0x53, 0x04, - 0xf3, 0x8d, 0x79, 0x8d, 0x9c, 0x9b, 0x91, 0xcd, 0x05, 0x3c, 0xd3, 0x43, 0x9f, 0x42, 0x89, 0xf7, - 0xdb, 0x0c, 0x44, 0x9d, 0xe2, 0x96, 0x17, 0x0e, 0x95, 0xb4, 0x80, 0x61, 0x1c, 0x7f, 0xd7, 0x35, - 0x58, 0xf6, 0x27, 0x1e, 0xef, 0xb6, 0xb2, 0xa1, 0x3b, 0xf0, 0xdd, 0x0e, 0x0d, 0x8f, 0x98, 0x7f, - 0x60, 0x84, 0x21, 0xb1, 0xf6, 0x47, 0xd4, 0x53, 0x63, 0x9c, 0x20, 0xd6, 0xa9, 0x33, 0xc4, 0x7a, - 0x05, 0x16, 0x89, 0xeb, 0x90, 0x80, 0x4a, 0x36, 0x52, 0xc4, 0x51, 0x91, 0xd3, 0x7f, 0x9e, 0x4c, - 0xd0, 0x20, 0xa0, 0x32, 0x05, 0x2e, 0xe2, 0x99, 0x40, 0xff, 0xfb, 0x34, 0x40, 0xbb, 0x67, 0x6c, - 0x2b, 0xf3, 0x4d, 0xc8, 0xef, 0x91, 0x91, 0xe3, 0x4e, 0x2f, 0x5b, 0xe0, 0x33, 0x7c, 0xcd, 0x90, - 0x86, 0x36, 0x84, 0x0e, 0x56, 0xba, 0x22, 0x2b, 0x98, 0xec, 0x7a, 0x34, 0x8c, 0xb3, 0x02, 0x51, - 0xe2, 0x14, 0xc4, 0x27, 0x5e, 0x3c, 0x33, 0xb2, 0xc0, 0x5d, 0x1f, 0x92, 0x90, 0x1e, 0x91, 0x69, - 0xb4, 0x2a, 0x55, 0x11, 0x6d, 0xf2, 0x6c, 0x21, 0xa0, 0xfe, 0x21, 0xb5, 0x57, 0x72, 0x22, 0x04, - 0xaf, 0xf2, 0x07, 0x2b, 0xb8, 0x24, 0x57, 0xb1, 0x76, 0xe5, 0x63, 0xc1, 0x08, 0x66, 0x55, 0xdf, - 0x28, 0x81, 0xbf, 0x07, 0x4b, 0x67, 0xfa, 0xf9, 0x52, 0x3a, 0xd6, 0xee, 0x3d, 0xf9, 0xa1, 0x96, - 0x55, 0x5f, 0x3f, 0xd2, 0xf2, 0xfa, 0x9f, 0x66, 0xe4, 0x3a, 0x52, 0xa3, 0x3a, 0xff, 0x4a, 0xad, - 0x20, 0xa2, 0xdf, 0x62, 0xae, 0x8a, 0xef, 0xb7, 0x2e, 0x5f, 0x5e, 0x9c, 0xde, 0x0b, 0x38, 0x8e, - 0x15, 0xd1, 0x1a, 0x94, 0xe4, 0xfc, 0x9b, 0x3c, 0x9e, 0xc4, 0xb0, 0x2e, 0x61, 0x90, 0x22, 0xae, - 0x89, 0xee, 0xc2, 0xf2, 0x78, 0xb2, 0xeb, 0x3a, 0xc1, 0x3e, 0xb5, 0x25, 0x26, 0x2b, 0x30, 0x4b, - 0xb1, 0x54, 0xc0, 0xb6, 0xa1, 0xac, 0x04, 0xa6, 0xa0, 0x76, 0x39, 0xe1, 0xd0, 0xbb, 0x57, 0x39, - 0x24, 0x55, 0x04, 0xe3, 0x2b, 0x8d, 0x67, 0x05, 0xbd, 0x09, 0x85, 0xc8, 0x59, 0xb4, 0x02, 0x99, - 0x41, 0xa3, 0xa7, 0x2d, 0x54, 0xae, 0x1d, 0x9f, 0x54, 0x4b, 0x91, 0x78, 0xd0, 0xe8, 0xf1, 0x9a, - 0x9d, 0x66, 0x4f, 0x4b, 0x9d, 0xad, 0xd9, 0x69, 0xf6, 0x2a, 0x59, 0x4e, 0x31, 0xf4, 0x3d, 0x28, - 0x25, 0x5a, 0x40, 0x6f, 0xc0, 0x62, 0xbb, 0xf3, 0x08, 0xb7, 0xfa, 0x7d, 0x6d, 0xa1, 0x72, 0xeb, - 0xf8, 0xa4, 0x8a, 0x12, 0xb5, 0x6d, 0x6f, 0xc8, 0xe7, 0x07, 0xbd, 0x06, 0xd9, 0xcd, 0x2e, 0x3f, - 0xba, 0x24, 0x97, 0x4c, 0x20, 0x36, 0x59, 0x10, 0x56, 0x6e, 0x28, 0xee, 0x92, 0x34, 0xac, 0xff, - 0x71, 0x0a, 0xf2, 0x92, 0x52, 0xcf, 0x9d, 0x28, 0x03, 0x16, 0xa3, 0x44, 0x4f, 0xf2, 0xfc, 0xb7, - 0x2e, 0xe6, 0xe4, 0x35, 0x45, 0xa1, 0x65, 0xf8, 0x45, 0x7a, 0x95, 0x8f, 0xa0, 0x9c, 0xac, 0xf8, - 0x46, 0xc1, 0xf7, 0x9b, 0x50, 0xe2, 0xf1, 0x1d, 0x71, 0xf3, 0x75, 0xc8, 0x4b, 0xda, 0x1f, 0x6f, - 0xa5, 0x17, 0x27, 0x08, 0x0a, 0x89, 0x1e, 0xc2, 0xa2, 0x4c, 0x2a, 0xa2, 0x2b, 0xb0, 0xd5, 0xcb, - 0x57, 0x11, 0x8e, 0xe0, 0xfa, 0xa7, 0x90, 0xed, 0x51, 0xea, 0xf3, 0xb1, 0xf7, 0x98, 0x4d, 0x67, - 0xa7, 0x8f, 0xca, 0x87, 0x6c, 0xda, 0x6e, 0xf2, 0x7c, 0xc8, 0xa6, 0x6d, 0x3b, 0xbe, 0xc1, 0x48, - 0x27, 0x6e, 0x30, 0x06, 0x50, 0x7e, 0x4a, 0x9d, 0xe1, 0x7e, 0x48, 0x6d, 0x61, 0xe8, 0x3d, 0xc8, - 0x8e, 0x69, 0xec, 0xfc, 0xca, 0xdc, 0x00, 0xa3, 0xd4, 0xc7, 0x02, 0xc5, 0xf7, 0x91, 0x23, 0xa1, - 0xad, 0x2e, 0x5e, 0x55, 0x49, 0xff, 0xbb, 0x34, 0x2c, 0xb7, 0x83, 0x60, 0x42, 0x3c, 0x2b, 0x22, - 0x26, 0x9f, 0x9c, 0x25, 0x26, 0x6f, 0xcf, 0xed, 0xe1, 0x19, 0x95, 0xb3, 0x17, 0x33, 0xea, 0x70, - 0x48, 0xc7, 0x87, 0x83, 0xfe, 0xef, 0xa9, 0xe8, 0xf6, 0xe5, 0x6e, 0x62, 0xb9, 0x57, 0x56, 0x8e, - 0x4f, 0xaa, 0x37, 0x93, 0x96, 0xe8, 0x8e, 0x77, 0xe0, 0xb1, 0x23, 0x0f, 0xbd, 0x0e, 0x39, 0xdc, - 0xea, 0xb4, 0x9e, 0x6a, 0x29, 0x19, 0x9e, 0x67, 0x40, 0x98, 0x7a, 0xf4, 0x88, 0x5b, 0xea, 0xb5, - 0x3a, 0x4d, 0x4e, 0x24, 0xd2, 0x73, 0x2c, 0xf5, 0xa8, 0x67, 0x3b, 0xde, 0x10, 0xbd, 0x01, 0xf9, - 0x76, 0xbf, 0xbf, 0x23, 0xf2, 0xe3, 0xef, 0x1e, 0x9f, 0x54, 0x6f, 0x9c, 0x41, 0xf1, 0x02, 0xb5, - 0x39, 0x88, 0xb3, 0x78, 0x4e, 0x31, 0xe6, 0x80, 0x38, 0x3d, 0x94, 0x20, 0xdc, 0x1d, 0xf0, 0xe4, - 0x3d, 0x37, 0x07, 0x84, 0x19, 0xff, 0xab, 0x96, 0xdb, 0x3f, 0xa5, 0x41, 0x33, 0x2c, 0x8b, 0x8e, - 0x43, 0x5e, 0xaf, 0x12, 0xa7, 0x01, 0x14, 0xc6, 0xfc, 0xcb, 0xa1, 0x11, 0x09, 0x78, 0x38, 0xf7, - 0xea, 0xff, 0x9c, 0x5e, 0x0d, 0x33, 0x97, 0x1a, 0xf6, 0xc8, 0x09, 0x02, 0x87, 0x79, 0x52, 0x86, - 0x63, 0x4b, 0x95, 0xff, 0x4c, 0xc1, 0x8d, 0x39, 0x08, 0x74, 0x0f, 0xb2, 0x3e, 0x73, 0xa3, 0x39, - 0xbc, 0x73, 0xd1, 0xc5, 0x1a, 0x57, 0xc5, 0x02, 0x89, 0x56, 0x01, 0xc8, 0x24, 0x64, 0x44, 0xb4, - 0x2f, 0x66, 0xaf, 0x80, 0x13, 0x12, 0xf4, 0x14, 0xf2, 0x01, 0xb5, 0x7c, 0x1a, 0x51, 0xc5, 0x4f, - 0xff, 0xaf, 0xde, 0xd7, 0xfa, 0xc2, 0x0c, 0x56, 0xe6, 0x2a, 0x35, 0xc8, 0x4b, 0x09, 0x0f, 0x7b, - 0x9b, 0x84, 0x44, 0x38, 0x5d, 0xc6, 0xe2, 0x9b, 0x47, 0x13, 0x71, 0x87, 0x51, 0x34, 0x11, 0x77, - 0xa8, 0xff, 0x75, 0x1a, 0xa0, 0xf5, 0x2c, 0xa4, 0xbe, 0x47, 0xdc, 0x86, 0x81, 0x5a, 0x89, 0xdd, - 0x5f, 0xf6, 0xf6, 0x9d, 0xb9, 0xd7, 0xad, 0xb1, 0x46, 0xad, 0x61, 0xcc, 0xd9, 0xff, 0x6f, 0x43, - 0x66, 0xe2, 0xab, 0xd7, 0x1c, 0x49, 0xf3, 0x76, 0xf0, 0x16, 0xe6, 0x32, 0xd4, 0x9a, 0x6d, 0x5b, - 0x99, 0x8b, 0xdf, 0x6c, 0x12, 0x0d, 0xcc, 0xdd, 0xba, 0xf8, 0xca, 0xb7, 0x88, 0x69, 0x51, 0x75, - 0x72, 0x94, 0xe5, 0xca, 0x6f, 0x18, 0x0d, 0xea, 0x87, 0x38, 0x6f, 0x11, 0xfe, 0xff, 0x5b, 0xed, - 0x6f, 0xef, 0x01, 0xcc, 0xba, 0x86, 0x56, 0x21, 0xd7, 0xd8, 0xe8, 0xf7, 0xb7, 0xb4, 0x05, 0xb9, - 0x81, 0xcf, 0xaa, 0x84, 0x58, 0xff, 0x59, 0x0a, 0x0a, 0x0d, 0x43, 0x1d, 0xab, 0x0d, 0xd0, 0xc4, - 0xae, 0xc4, 0xbd, 0x33, 0xe9, 0xb3, 0xb1, 0xe3, 0x4f, 0xd5, 0xc6, 0x72, 0x49, 0xce, 0xb6, 0xcc, - 0x55, 0xb8, 0xd7, 0x2d, 0xa1, 0x80, 0x30, 0x94, 0xa9, 0x1a, 0x04, 0xd3, 0x22, 0xd1, 0x1e, 0xbf, - 0x7a, 0xf9, 0x60, 0x49, 0xf6, 0x3d, 0x2b, 0x07, 0xb8, 0x14, 0x19, 0x69, 0x90, 0x40, 0x7f, 0x02, - 0x37, 0xba, 0xbe, 0xb5, 0x4f, 0x83, 0x50, 0x36, 0xaa, 0xfc, 0xfd, 0x14, 0xee, 0x84, 0x24, 0x38, - 0x30, 0xf7, 0x9d, 0x20, 0x64, 0xfe, 0xd4, 0xf4, 0x69, 0x48, 0x3d, 0x5e, 0x6f, 0x8a, 0xb7, 0x1f, - 0x75, 0xd3, 0x72, 0x9b, 0x63, 0x36, 0x25, 0x04, 0x47, 0x88, 0x2d, 0x0e, 0xd0, 0xdb, 0x50, 0xe6, - 0x7c, 0xb7, 0x49, 0xf7, 0xc8, 0xc4, 0x0d, 0x03, 0x9e, 0x49, 0xb9, 0x6c, 0x68, 0xbe, 0xf2, 0x81, - 0x50, 0x74, 0xd9, 0x50, 0x7e, 0xea, 0x3f, 0x05, 0xad, 0xe9, 0x04, 0x63, 0x12, 0x5a, 0xfb, 0xd1, - 0x15, 0x12, 0x6a, 0x82, 0xb6, 0x4f, 0x89, 0x1f, 0xee, 0x52, 0x12, 0x9a, 0x63, 0xea, 0x3b, 0xcc, - 0xbe, 0x7a, 0x3c, 0xaf, 0xc5, 0x2a, 0x3d, 0xa1, 0xa1, 0xff, 0x57, 0x0a, 0x00, 0x93, 0xbd, 0x88, - 0xfb, 0xfc, 0x00, 0xae, 0x07, 0x1e, 0x19, 0x07, 0xfb, 0x2c, 0x34, 0x1d, 0x2f, 0xa4, 0xfe, 0x21, - 0x71, 0xd5, 0x4d, 0x80, 0x16, 0x55, 0xb4, 0x95, 0x1c, 0xbd, 0x07, 0xe8, 0x80, 0xd2, 0xb1, 0xc9, - 0x5c, 0xdb, 0x8c, 0x2a, 0xe5, 0xcb, 0x54, 0x16, 0x6b, 0xbc, 0xa6, 0xeb, 0xda, 0xfd, 0x48, 0x8e, - 0xea, 0xb0, 0xca, 0xbb, 0x4f, 0xbd, 0xd0, 0x77, 0x68, 0x60, 0xee, 0x31, 0xdf, 0x0c, 0x5c, 0x76, - 0x64, 0xee, 0x31, 0xd7, 0x65, 0x47, 0xd4, 0x8f, 0x2e, 0x59, 0x2a, 0x2e, 0x1b, 0xb6, 0x24, 0x68, - 0x83, 0xf9, 0x7d, 0x97, 0x1d, 0x6d, 0x44, 0x08, 0x4e, 0x90, 0x66, 0x7d, 0x0e, 0x1d, 0xeb, 0x20, - 0x22, 0x48, 0xb1, 0x74, 0xe0, 0x58, 0x07, 0xe8, 0x0d, 0x58, 0xa2, 0x2e, 0x15, 0xb9, 0xb6, 0x44, - 0xe5, 0x04, 0xaa, 0x1c, 0x09, 0x39, 0x48, 0xff, 0x0c, 0xb4, 0x96, 0x67, 0xf9, 0xd3, 0x71, 0x62, - 0xce, 0xdf, 0x03, 0xc4, 0xb7, 0x23, 0xd3, 0x65, 0xd6, 0x81, 0x39, 0x22, 0x1e, 0x19, 0x72, 0xbf, - 0xe4, 0x6b, 0x88, 0xc6, 0x6b, 0xb6, 0x98, 0x75, 0xb0, 0xad, 0xe4, 0xfa, 0x87, 0x00, 0xfd, 0xb1, - 0x4f, 0x89, 0xdd, 0xe5, 0xe7, 0x36, 0x1f, 0x3a, 0x51, 0x32, 0x6d, 0xf5, 0xe0, 0xc2, 0x7c, 0xb5, - 0xa8, 0x34, 0x59, 0xd1, 0x8c, 0xe5, 0xfa, 0xaf, 0xc2, 0x8d, 0x9e, 0x4b, 0x2c, 0xf1, 0xf8, 0xd8, - 0x8b, 0xaf, 0xf7, 0xd1, 0x43, 0xc8, 0x4b, 0xa8, 0x9a, 0xc9, 0xb9, 0x81, 0x3d, 0x6b, 0x73, 0x73, - 0x01, 0x2b, 0x7c, 0xbd, 0x0c, 0x30, 0xb3, 0xa3, 0x3f, 0x83, 0x62, 0x6c, 0x1e, 0x55, 0x81, 0x27, - 0x9b, 0x3c, 0xba, 0x1d, 0x4f, 0x65, 0x87, 0x45, 0x9c, 0x14, 0xa1, 0x36, 0x94, 0xc6, 0xb1, 0xf2, - 0xa5, 0xc4, 0x69, 0x8e, 0xd3, 0x38, 0xa9, 0xab, 0x7f, 0x02, 0xf0, 0x13, 0xe6, 0x78, 0x03, 0x76, - 0x40, 0x3d, 0xf1, 0xa2, 0xc4, 0xf3, 0x22, 0x1a, 0x0d, 0x84, 0x2a, 0x89, 0xb4, 0x4f, 0x8e, 0x62, - 0xfc, 0xb0, 0x22, 0x8b, 0xfa, 0x1f, 0xa4, 0x21, 0x8f, 0x19, 0x0b, 0x1b, 0x06, 0xaa, 0x42, 0xde, - 0x22, 0x66, 0xb4, 0x35, 0x95, 0xeb, 0xc5, 0xd3, 0x17, 0x6b, 0xb9, 0x86, 0xf1, 0x98, 0x4e, 0x71, - 0xce, 0x22, 0x8f, 0xe9, 0x34, 0xb9, 0xdd, 0xa5, 0x2f, 0xda, 0xee, 0xd0, 0x3d, 0x28, 0x2b, 0x90, - 0xb9, 0x4f, 0x82, 0x7d, 0x99, 0xcd, 0xd4, 0x97, 0x4f, 0x5f, 0xac, 0x81, 0x44, 0x6e, 0x92, 0x60, - 0x1f, 0x83, 0x44, 0xf3, 0x6f, 0xd4, 0x82, 0xd2, 0x17, 0xcc, 0xf1, 0xcc, 0x50, 0x74, 0x42, 0x5d, - 0x2c, 0xcd, 0x9d, 0x8a, 0x59, 0x57, 0xd5, 0x0b, 0x24, 0x7c, 0x31, 0xeb, 0x7c, 0x0b, 0x96, 0x7c, - 0xc6, 0x42, 0xd3, 0x57, 0xef, 0xec, 0x2a, 0x67, 0xad, 0xce, 0xbd, 0xca, 0x64, 0x2c, 0xc4, 0x0a, - 0x87, 0xcb, 0x7e, 0xa2, 0xa4, 0xff, 0x43, 0x0a, 0x4a, 0xdc, 0x35, 0x67, 0xcf, 0xb1, 0x38, 0xbf, - 0xf9, 0xe6, 0xc7, 0xee, 0x6d, 0xc8, 0x58, 0x81, 0xaf, 0x86, 0x48, 0x9c, 0x3b, 0x8d, 0x3e, 0xc6, - 0x5c, 0x86, 0x3e, 0x83, 0xbc, 0xca, 0x84, 0xe5, 0x89, 0xab, 0x5f, 0xcd, 0xc4, 0x54, 0x4f, 0x95, - 0x9e, 0x88, 0xae, 0x99, 0x77, 0xf2, 0xd8, 0xc1, 0x49, 0x11, 0xba, 0x05, 0x69, 0x4b, 0x76, 0x5e, - 0x3d, 0x58, 0x37, 0x3a, 0x38, 0x6d, 0x79, 0xfa, 0xdf, 0xa6, 0x60, 0x69, 0xb6, 0x02, 0xf9, 0x7c, - 0xde, 0x81, 0x62, 0x30, 0xd9, 0x0d, 0xa6, 0x41, 0x48, 0x47, 0xd1, 0xdb, 0x57, 0x2c, 0x40, 0x6d, - 0x28, 0x12, 0x77, 0xc8, 0x7c, 0x27, 0xdc, 0x1f, 0xa9, 0x24, 0x6c, 0xfe, 0x29, 0x99, 0xb4, 0x59, - 0x33, 0x22, 0x15, 0x3c, 0xd3, 0x8e, 0x8e, 0xbc, 0x8c, 0x70, 0x56, 0x1c, 0x79, 0xaf, 0x43, 0xd9, - 0x25, 0x23, 0x71, 0x35, 0xc0, 0x73, 0x7b, 0xd1, 0x8f, 0x2c, 0x2e, 0x29, 0xd9, 0xc0, 0x19, 0x51, - 0x5d, 0x87, 0x62, 0x6c, 0x0c, 0x5d, 0x83, 0x92, 0xd1, 0xea, 0x9b, 0xf7, 0xd7, 0x1f, 0x9a, 0x8f, - 0x1a, 0xdb, 0xda, 0x82, 0xa2, 0x65, 0x7f, 0x91, 0x82, 0x25, 0xb5, 0x3f, 0x28, 0xaa, 0xfb, 0x06, - 0x2c, 0xfa, 0x64, 0x2f, 0x8c, 0xc8, 0x78, 0x56, 0xc6, 0x28, 0xdf, 0x72, 0x39, 0x19, 0xe7, 0x55, - 0xf3, 0xc9, 0x78, 0xe2, 0x35, 0x36, 0x73, 0xe9, 0x6b, 0x6c, 0xf6, 0xff, 0xe5, 0x35, 0x56, 0xff, - 0xb3, 0x34, 0x5c, 0x53, 0xac, 0x29, 0xde, 0x8e, 0xde, 0x81, 0xa2, 0x24, 0x50, 0xb3, 0x54, 0x42, - 0x3c, 0x00, 0x4a, 0x5c, 0xbb, 0x89, 0x0b, 0xb2, 0xba, 0x6d, 0xf3, 0xdc, 0x56, 0x41, 0x13, 0xbf, - 0x2d, 0x00, 0x29, 0xea, 0xf0, 0xc4, 0xac, 0x09, 0xd9, 0x3d, 0xc7, 0xa5, 0x2a, 0xce, 0xe6, 0x5e, - 0xfb, 0x9e, 0x6b, 0x5e, 0x3c, 0x50, 0x0c, 0x44, 0x76, 0xbc, 0xb9, 0x80, 0x85, 0x76, 0xe5, 0xb7, - 0x01, 0x66, 0xd2, 0xb9, 0x09, 0x20, 0x27, 0x59, 0xea, 0x2e, 0x2d, 0x22, 0x59, 0xed, 0x26, 0xe6, - 0x32, 0x5e, 0x35, 0x74, 0x6c, 0xb5, 0x01, 0x88, 0xaa, 0x47, 0xbc, 0x6a, 0xe8, 0xd8, 0xf1, 0x2b, - 0x49, 0xf6, 0x8a, 0x57, 0x92, 0x7a, 0x21, 0xba, 0xd1, 0xd1, 0xb7, 0xe0, 0x56, 0xdd, 0x25, 0xd6, - 0x81, 0xeb, 0x04, 0x21, 0xb5, 0x93, 0x2b, 0x74, 0x1d, 0xf2, 0x67, 0xf8, 0xcd, 0x65, 0x17, 0x68, - 0x0a, 0xa9, 0xff, 0x5b, 0x0a, 0xca, 0x9b, 0x94, 0xb8, 0xe1, 0xfe, 0xec, 0x16, 0x22, 0xa4, 0x41, - 0xa8, 0xb6, 0x6b, 0xf1, 0x8d, 0x3e, 0x80, 0x42, 0x7c, 0x28, 0x5f, 0xf9, 0x92, 0x11, 0x43, 0xd1, - 0x03, 0x58, 0xe4, 0x31, 0xcd, 0x26, 0x11, 0xaf, 0xbe, 0xec, 0x92, 0x5c, 0x21, 0xf9, 0x16, 0xed, - 0x53, 0x71, 0x0a, 0x8b, 0x41, 0xc9, 0xe1, 0xa8, 0x88, 0x7e, 0x05, 0xca, 0xe2, 0x8e, 0x37, 0x22, - 0x1d, 0xb9, 0xab, 0x6c, 0x96, 0xe4, 0x33, 0x8d, 0x24, 0x1c, 0xff, 0x93, 0x82, 0x9b, 0xdb, 0x64, - 0xba, 0x4b, 0xd5, 0x32, 0xa5, 0x36, 0xa6, 0x16, 0xf3, 0x6d, 0xd4, 0x4b, 0x2e, 0xef, 0x4b, 0x5e, - 0x7d, 0xe6, 0x29, 0xcf, 0x5f, 0xe5, 0x11, 0xd7, 0x4f, 0x27, 0xb8, 0xfe, 0x4d, 0xc8, 0x79, 0xcc, - 0xb3, 0xa8, 0x5a, 0xfb, 0xb2, 0xa0, 0x3b, 0xc9, 0xa5, 0x5d, 0x89, 0x1f, 0x64, 0xc4, 0x73, 0x4a, - 0x87, 0x85, 0x71, 0x6b, 0xe8, 0x33, 0xa8, 0xf4, 0x5b, 0x0d, 0xdc, 0x1a, 0xd4, 0xbb, 0x3f, 0x35, - 0xfb, 0xc6, 0x56, 0xdf, 0x58, 0xbf, 0x67, 0xf6, 0xba, 0x5b, 0x9f, 0xdf, 0x7f, 0x70, 0xef, 0x03, - 0x2d, 0x55, 0xa9, 0x1e, 0x9f, 0x54, 0xef, 0x74, 0x8c, 0xc6, 0x96, 0x8c, 0xe5, 0x5d, 0xf6, 0xac, - 0x4f, 0xdc, 0x80, 0xac, 0xdf, 0xeb, 0x31, 0x77, 0xca, 0x31, 0xfa, 0x49, 0x0a, 0xca, 0xc9, 0xdd, - 0x3e, 0x79, 0x88, 0xa5, 0x2e, 0x3c, 0xc4, 0x66, 0x67, 0x61, 0xfa, 0x82, 0xb3, 0x70, 0x03, 0x6e, - 0x5a, 0x3e, 0x0b, 0x02, 0x33, 0x70, 0x86, 0x1e, 0xb5, 0xcd, 0xc8, 0xa6, 0xe8, 0x67, 0xfd, 0x3b, - 0xa7, 0x2f, 0xd6, 0xae, 0x37, 0x78, 0x7d, 0x5f, 0x54, 0x2b, 0xf3, 0xd7, 0xad, 0x84, 0x48, 0xb4, - 0xf4, 0xee, 0x2f, 0x33, 0x50, 0x8c, 0xaf, 0x69, 0xf9, 0x92, 0xe1, 0x39, 0xb2, 0x1a, 0x8a, 0x58, - 0xde, 0xa1, 0x47, 0xe8, 0xf5, 0x59, 0x76, 0xfc, 0x99, 0x7c, 0x97, 0x8a, 0xab, 0xa3, 0xcc, 0xf8, - 0x4d, 0x28, 0x18, 0xfd, 0x7e, 0xfb, 0x51, 0xa7, 0xd5, 0xd4, 0xbe, 0x4c, 0x55, 0xbe, 0x73, 0x7c, - 0x52, 0xbd, 0x1e, 0x83, 0x8c, 0x40, 0x7a, 0x2a, 0x50, 0x8d, 0x46, 0xab, 0x37, 0x68, 0x35, 0xb5, - 0xe7, 0xe9, 0xf3, 0x28, 0x91, 0xed, 0x89, 0xd7, 0xe5, 0x62, 0x0f, 0xb7, 0x7a, 0x06, 0xe6, 0x0d, - 0x7e, 0x99, 0x96, 0x49, 0xfb, 0xac, 0x45, 0x9f, 0x8e, 0x89, 0xcf, 0xdb, 0x5c, 0x8d, 0x7e, 0x65, - 0xf1, 0x3c, 0x23, 0x5f, 0x20, 0x67, 0x77, 0xce, 0x94, 0xd8, 0x53, 0xde, 0x9a, 0xb8, 0xec, 0x17, - 0x66, 0x32, 0xe7, 0x5a, 0xeb, 0xf3, 0x40, 0xe5, 0x56, 0x74, 0x58, 0xc4, 0x3b, 0x9d, 0x0e, 0x07, - 0x3d, 0xcf, 0x9e, 0xeb, 0x1d, 0x9e, 0x78, 0x1e, 0xc7, 0xdc, 0x85, 0x42, 0xf4, 0x16, 0xa0, 0x7d, - 0x99, 0x3d, 0xe7, 0x50, 0x23, 0x7a, 0xc8, 0x10, 0x0d, 0x6e, 0xee, 0x0c, 0xc4, 0x8f, 0x40, 0x9e, - 0xe7, 0xce, 0x37, 0xb8, 0x3f, 0x09, 0x6d, 0x76, 0xe4, 0xf1, 0x09, 0x56, 0xf7, 0x03, 0x5f, 0xe6, - 0x64, 0x32, 0x15, 0x63, 0xd4, 0xe5, 0xc0, 0x9b, 0x50, 0xc0, 0xad, 0x9f, 0xc8, 0xdf, 0x8b, 0x3c, - 0xcf, 0x9f, 0xb3, 0x83, 0xe9, 0x17, 0xd4, 0x52, 0xad, 0x75, 0x71, 0x6f, 0xd3, 0x10, 0x43, 0x7e, - 0x1e, 0xd5, 0xf5, 0xc7, 0xfb, 0xc4, 0xa3, 0xf6, 0xec, 0x19, 0x36, 0xae, 0x7a, 0xf7, 0xd7, 0xa0, - 0x10, 0xd1, 0x06, 0xb4, 0x0a, 0xf9, 0xa7, 0x5d, 0xfc, 0xb8, 0x85, 0xb5, 0x05, 0x39, 0x86, 0x51, - 0xcd, 0x53, 0x49, 0xdf, 0xaa, 0xb0, 0xb8, 0x6d, 0x74, 0x8c, 0x47, 0x2d, 0x1c, 0x5d, 0xdd, 0x45, - 0x00, 0x75, 0xf6, 0x55, 0x34, 0xd5, 0x40, 0x6c, 0xb3, 0xbe, 0xf2, 0xd5, 0xd7, 0xab, 0x0b, 0xbf, - 0xf8, 0x7a, 0x75, 0xe1, 0xf9, 0xe9, 0x6a, 0xea, 0xab, 0xd3, 0xd5, 0xd4, 0xcf, 0x4f, 0x57, 0x53, - 0xff, 0x72, 0xba, 0x9a, 0xda, 0xcd, 0x8b, 0x1d, 0xe3, 0xc1, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, - 0x55, 0x06, 0x85, 0xd6, 0xa6, 0x29, 0x00, 0x00, + // 4333 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x5a, 0x4f, 0x6c, 0x24, 0xc7, + 0x5a, 0xf7, 0xfc, 0xf5, 0xcc, 0x37, 0x63, 0xbb, 0xb7, 0xd6, 0xd9, 0x78, 0x27, 0x1b, 0x7b, 0xd2, + 0xc9, 0xbe, 0xfc, 0x79, 0xd1, 0x64, 0xd7, 0xfb, 0x12, 0x6d, 0x12, 0x5e, 0x92, 0xf9, 0xe7, 0xf5, + 0xbc, 0xb5, 0x67, 0x46, 0x35, 0xe3, 0xdd, 0x97, 0x03, 0xb4, 0xca, 0xdd, 0xe5, 0x71, 0xc7, 0x3d, + 0x5d, 0x43, 0x77, 0x8f, 0xbd, 0x03, 0x42, 0xac, 0x38, 0x00, 0xf2, 0x89, 0x23, 0x12, 0xf2, 0x09, + 0x4e, 0x1c, 0xb8, 0x70, 0x40, 0x70, 0x21, 0x48, 0x1c, 0x72, 0xe3, 0x01, 0x12, 0x7a, 0x02, 0xc9, + 0x10, 0x23, 0x71, 0x43, 0x70, 0x79, 0xe2, 0x02, 0x12, 0xaa, 0x3f, 0xdd, 0xd3, 0xf6, 0x8e, 0xed, + 0x0d, 0xe1, 0x62, 0x77, 0x7d, 0xf5, 0xfb, 0xbe, 0xaa, 0xfa, 0xea, 0xab, 0xaa, 0xdf, 0x57, 0x35, + 0x50, 0x08, 0x26, 0x23, 0xea, 0x57, 0x46, 0x1e, 0x0b, 0x18, 0x42, 0x16, 0x33, 0x0f, 0xa8, 0x57, + 0xf1, 0x8f, 0x88, 0x37, 0x3c, 0xb0, 0x83, 0xca, 0xe1, 0xfd, 0xd2, 0xda, 0x80, 0xb1, 0x81, 0x43, + 0x3f, 0x10, 0x88, 0xdd, 0xf1, 0xde, 0x07, 0x81, 0x3d, 0xa4, 0x7e, 0x40, 0x86, 0x23, 0xa9, 0x54, + 0x5a, 0xbd, 0x08, 0xb0, 0xc6, 0x1e, 0x09, 0x6c, 0xe6, 0xaa, 0xfa, 0xe5, 0x01, 0x1b, 0x30, 0xf1, + 0xf9, 0x01, 0xff, 0x92, 0x52, 0x7d, 0x0d, 0xe6, 0x9f, 0x50, 0xcf, 0xb7, 0x99, 0x8b, 0x96, 0x21, + 0x63, 0xbb, 0x16, 0x7d, 0xb6, 0x92, 0x28, 0x27, 0xde, 0x49, 0x63, 0x59, 0xd0, 0xef, 0x01, 0xb4, + 0xf8, 0x47, 0xd3, 0x0d, 0xbc, 0x09, 0xd2, 0x20, 0x75, 0x40, 0x27, 0x02, 0x91, 0xc7, 0xfc, 0x93, + 0x4b, 0x0e, 0x89, 0xb3, 0x92, 0x94, 0x92, 0x43, 0xe2, 0xe8, 0xdf, 0x26, 0xa0, 0x50, 0x75, 0x5d, + 0x16, 0x88, 0xd6, 0x7d, 0x84, 0x20, 0xed, 0x92, 0x21, 0x55, 0x4a, 0xe2, 0x1b, 0xd5, 0x21, 0xeb, + 0x90, 0x5d, 0xea, 0xf8, 0x2b, 0xc9, 0x72, 0xea, 0x9d, 0xc2, 0xfa, 0x0f, 0x2b, 0x2f, 0x0e, 0xb9, + 0x12, 0x33, 0x52, 0xd9, 0x12, 0x68, 0xd1, 0x09, 0xac, 0x54, 0xd1, 0x67, 0x30, 0x6f, 0xbb, 0x96, + 0x6d, 0x52, 0x7f, 0x25, 0x2d, 0xac, 0xac, 0xce, 0xb2, 0x32, 0xed, 0x7d, 0x2d, 0xfd, 0xcd, 0xe9, + 0xda, 0x1c, 0x0e, 0x95, 0x4a, 0x1f, 0x43, 0x21, 0x66, 0x76, 0xc6, 0xd8, 0x96, 0x21, 0x73, 0x48, + 0x9c, 0x31, 0x55, 0xa3, 0x93, 0x85, 0x4f, 0x92, 0x0f, 0x13, 0xfa, 0x97, 0x90, 0xc7, 0xd4, 0x67, + 0x63, 0xcf, 0xa4, 0x3e, 0x7a, 0x17, 0xf2, 0x2e, 0x71, 0x99, 0x61, 0x8e, 0xc6, 0xbe, 0x50, 0x4f, + 0xd5, 0x8a, 0x67, 0xa7, 0x6b, 0xb9, 0x36, 0x71, 0x59, 0xbd, 0xbb, 0xe3, 0xe3, 0x1c, 0xaf, 0xae, + 0x8f, 0xc6, 0x3e, 0x7a, 0x03, 0x8a, 0x43, 0x3a, 0x64, 0xde, 0xc4, 0xd8, 0x9d, 0x04, 0xd4, 0x17, + 0x86, 0x53, 0xb8, 0x20, 0x65, 0x35, 0x2e, 0xd2, 0x7f, 0x2f, 0x01, 0xcb, 0xa1, 0x6d, 0x4c, 0x7f, + 0x75, 0x6c, 0x7b, 0x74, 0x48, 0xdd, 0xc0, 0x47, 0x1f, 0x42, 0xd6, 0xb1, 0x87, 0x76, 0x20, 0xdb, + 0x28, 0xac, 0xbf, 0x3e, 0x6b, 0xb4, 0x51, 0xaf, 0xb0, 0x02, 0xa3, 0x2a, 0x14, 0x3d, 0xea, 0x53, + 0xef, 0x50, 0x7a, 0x52, 0x34, 0x79, 0xad, 0xf2, 0x39, 0x15, 0x7d, 0x03, 0x72, 0x5d, 0x87, 0x04, + 0x7b, 0xcc, 0x1b, 0x22, 0x1d, 0x8a, 0xc4, 0x33, 0xf7, 0xed, 0x80, 0x9a, 0xc1, 0xd8, 0x0b, 0x67, + 0xf5, 0x9c, 0x0c, 0xdd, 0x82, 0x24, 0x93, 0x0d, 0xe5, 0x6b, 0xd9, 0xb3, 0xd3, 0xb5, 0x64, 0xa7, + 0x87, 0x93, 0xcc, 0xd7, 0x3f, 0x85, 0x1b, 0x5d, 0x67, 0x3c, 0xb0, 0xdd, 0x06, 0xf5, 0x4d, 0xcf, + 0x1e, 0x71, 0xeb, 0x3c, 0x3c, 0x78, 0xec, 0x87, 0xe1, 0xc1, 0xbf, 0xa3, 0x90, 0x49, 0x4e, 0x43, + 0x46, 0xff, 0x9d, 0x24, 0xdc, 0x68, 0xba, 0x03, 0xdb, 0xa5, 0x71, 0xed, 0xbb, 0xb0, 0x48, 0x85, + 0xd0, 0x38, 0x94, 0x61, 0xac, 0xec, 0x2c, 0x48, 0x69, 0x18, 0xdb, 0xad, 0x0b, 0xf1, 0x76, 0x7f, + 0xd6, 0xf0, 0x5f, 0xb0, 0x3e, 0x33, 0xea, 0x9a, 0x30, 0x3f, 0x12, 0x83, 0xf0, 0x57, 0x52, 0xc2, + 0xd6, 0xdd, 0x59, 0xb6, 0x5e, 0x18, 0x67, 0x18, 0x7c, 0x4a, 0xf7, 0xfb, 0x04, 0xdf, 0xbf, 0x26, + 0x60, 0xa9, 0xcd, 0xac, 0x73, 0x7e, 0x28, 0x41, 0x6e, 0x9f, 0xf9, 0x41, 0x6c, 0xa1, 0x45, 0x65, + 0xf4, 0x10, 0x72, 0x23, 0x35, 0x7d, 0x6a, 0xf6, 0xef, 0xcc, 0xee, 0xb2, 0xc4, 0xe0, 0x08, 0x8d, + 0x3e, 0x85, 0xbc, 0x17, 0xc6, 0xc4, 0x4a, 0xea, 0x65, 0x02, 0x67, 0x8a, 0x47, 0x3f, 0x86, 0xac, + 0x9c, 0x84, 0x95, 0xb4, 0xd0, 0xbc, 0xfb, 0x52, 0x3e, 0xc7, 0x4a, 0x49, 0xff, 0x79, 0x02, 0x34, + 0x4c, 0xf6, 0x82, 0x6d, 0x3a, 0xdc, 0xa5, 0x5e, 0x2f, 0x20, 0xc1, 0xd8, 0x47, 0xb7, 0x20, 0xeb, + 0x50, 0x62, 0x51, 0x4f, 0x0c, 0x32, 0x87, 0x55, 0x09, 0xed, 0xf0, 0x20, 0x27, 0xe6, 0x3e, 0xd9, + 0xb5, 0x1d, 0x3b, 0x98, 0x88, 0x61, 0x2e, 0xce, 0x9e, 0xe5, 0x8b, 0x36, 0x2b, 0x38, 0xa6, 0x88, + 0xcf, 0x99, 0x41, 0x2b, 0x30, 0x3f, 0xa4, 0xbe, 0x4f, 0x06, 0x54, 0x8c, 0x3e, 0x8f, 0xc3, 0xa2, + 0xfe, 0x29, 0x14, 0xe3, 0x7a, 0xa8, 0x00, 0xf3, 0x3b, 0xed, 0xc7, 0xed, 0xce, 0xd3, 0xb6, 0x36, + 0x87, 0x96, 0xa0, 0xb0, 0xd3, 0xc6, 0xcd, 0x6a, 0x7d, 0xb3, 0x5a, 0xdb, 0x6a, 0x6a, 0x09, 0xb4, + 0x00, 0xf9, 0x69, 0x31, 0xa9, 0xff, 0x69, 0x02, 0x80, 0x4f, 0xa0, 0x1a, 0xd4, 0x27, 0x90, 0xf1, + 0x03, 0x12, 0xc8, 0x89, 0x5b, 0x5c, 0x7f, 0x6b, 0x56, 0xaf, 0xa7, 0xf0, 0x0a, 0xff, 0x47, 0xb1, + 0x54, 0x89, 0xf7, 0x30, 0x79, 0xae, 0x87, 0x7c, 0x0d, 0x11, 0xcb, 0xf2, 0x54, 0xc7, 0xc5, 0xb7, + 0xfe, 0x29, 0x64, 0x84, 0xf6, 0xf9, 0xee, 0xe6, 0x20, 0xdd, 0xe0, 0x5f, 0x09, 0x94, 0x87, 0x0c, + 0x6e, 0x56, 0x1b, 0x5f, 0x6a, 0x49, 0xa4, 0x41, 0xb1, 0xd1, 0xea, 0xd5, 0x3b, 0xed, 0x76, 0xb3, + 0xde, 0x6f, 0x36, 0xb4, 0x94, 0x7e, 0x17, 0x32, 0xad, 0x21, 0xb7, 0x7c, 0x87, 0x47, 0xc5, 0x1e, + 0xf5, 0xa8, 0x6b, 0x86, 0xc1, 0x36, 0x15, 0xe8, 0x3f, 0xcb, 0x43, 0x66, 0x9b, 0x8d, 0xdd, 0x00, + 0xad, 0xc7, 0x56, 0xf6, 0xe2, 0xec, 0xcd, 0x59, 0x00, 0x2b, 0xfd, 0xc9, 0x88, 0xaa, 0x95, 0x7f, + 0x0b, 0xb2, 0x32, 0x7e, 0xd4, 0x70, 0x54, 0x89, 0xcb, 0x03, 0xe2, 0x0d, 0x68, 0xa0, 0xc6, 0xa3, + 0x4a, 0xe8, 0x1d, 0xc8, 0x79, 0x94, 0x58, 0xcc, 0x75, 0x26, 0x22, 0xcc, 0x72, 0x72, 0xeb, 0xc5, + 0x94, 0x58, 0x1d, 0xd7, 0x99, 0xe0, 0xa8, 0x16, 0x6d, 0x42, 0x71, 0xd7, 0x76, 0x2d, 0x83, 0x8d, + 0xe4, 0x3e, 0x98, 0xb9, 0x3c, 0x28, 0x65, 0xaf, 0x6a, 0xb6, 0x6b, 0x75, 0x24, 0x18, 0x17, 0x76, + 0xa7, 0x05, 0xd4, 0x86, 0xc5, 0x43, 0xe6, 0x8c, 0x87, 0x34, 0xb2, 0x95, 0x15, 0xb6, 0xde, 0xbe, + 0xdc, 0xd6, 0x13, 0x81, 0x0f, 0xad, 0x2d, 0x1c, 0xc6, 0x8b, 0xe8, 0x31, 0x2c, 0x04, 0xc3, 0xd1, + 0x9e, 0x1f, 0x99, 0x9b, 0x17, 0xe6, 0x7e, 0x70, 0x85, 0xc3, 0x38, 0x3c, 0xb4, 0x56, 0x0c, 0x62, + 0xa5, 0xd2, 0x6f, 0xa5, 0xa0, 0x10, 0xeb, 0x39, 0xea, 0x41, 0x61, 0xe4, 0xb1, 0x11, 0x19, 0x88, + 0xbd, 0x5c, 0xcd, 0xc5, 0xfd, 0x97, 0x1a, 0x75, 0xa5, 0x3b, 0x55, 0xc4, 0x71, 0x2b, 0xfa, 0x49, + 0x12, 0x0a, 0xb1, 0x4a, 0xf4, 0x1e, 0xe4, 0x70, 0x17, 0xb7, 0x9e, 0x54, 0xfb, 0x4d, 0x6d, 0xae, + 0x74, 0xe7, 0xf8, 0xa4, 0xbc, 0x22, 0xac, 0xc5, 0x0d, 0x74, 0x3d, 0xfb, 0x90, 0x87, 0xde, 0x3b, + 0x30, 0x1f, 0x42, 0x13, 0xa5, 0xd7, 0x8e, 0x4f, 0xca, 0xaf, 0x5e, 0x84, 0xc6, 0x90, 0xb8, 0xb7, + 0x59, 0xc5, 0xcd, 0x86, 0x96, 0x9c, 0x8d, 0xc4, 0xbd, 0x7d, 0xe2, 0x51, 0x0b, 0xfd, 0x00, 0xb2, + 0x0a, 0x98, 0x2a, 0x95, 0x8e, 0x4f, 0xca, 0xb7, 0x2e, 0x02, 0xa7, 0x38, 0xdc, 0xdb, 0xaa, 0x3e, + 0x69, 0x6a, 0xe9, 0xd9, 0x38, 0xdc, 0x73, 0xc8, 0x21, 0x45, 0x6f, 0x41, 0x46, 0xc2, 0x32, 0xa5, + 0xdb, 0xc7, 0x27, 0xe5, 0x57, 0x5e, 0x30, 0xc7, 0x51, 0xa5, 0x95, 0xdf, 0xfd, 0xc3, 0xd5, 0xb9, + 0xbf, 0xf8, 0xa3, 0x55, 0xed, 0x62, 0x75, 0xe9, 0xbf, 0x13, 0xb0, 0x70, 0x6e, 0xca, 0x91, 0x0e, + 0x59, 0x97, 0x99, 0x6c, 0x24, 0xb7, 0xf8, 0x5c, 0x0d, 0xce, 0x4e, 0xd7, 0xb2, 0x6d, 0x56, 0x67, + 0xa3, 0x09, 0x56, 0x35, 0xe8, 0xf1, 0x85, 0x43, 0xea, 0xc1, 0x4b, 0xc6, 0xd3, 0xcc, 0x63, 0xea, + 0x73, 0x58, 0xb0, 0x3c, 0xfb, 0x90, 0x7a, 0x86, 0xc9, 0xdc, 0x3d, 0x7b, 0xa0, 0xb6, 0xef, 0xd2, + 0x2c, 0x9b, 0x0d, 0x01, 0xc4, 0x45, 0xa9, 0x50, 0x17, 0xf8, 0xef, 0x71, 0x40, 0x95, 0x9e, 0x40, + 0x31, 0x1e, 0xa1, 0xe8, 0x75, 0x00, 0xdf, 0xfe, 0x35, 0xaa, 0x38, 0x8f, 0x60, 0x48, 0x38, 0xcf, + 0x25, 0x82, 0xf1, 0xa0, 0xb7, 0x21, 0x3d, 0x64, 0x96, 0xb4, 0xb3, 0x50, 0xbb, 0xc9, 0xcf, 0xc9, + 0x7f, 0x3c, 0x5d, 0x2b, 0x30, 0xbf, 0xb2, 0x61, 0x3b, 0x74, 0x9b, 0x59, 0x14, 0x0b, 0x80, 0x7e, + 0x08, 0x69, 0xbe, 0x55, 0xa0, 0xd7, 0x20, 0x5d, 0x6b, 0xb5, 0x1b, 0xda, 0x5c, 0xe9, 0xc6, 0xf1, + 0x49, 0x79, 0x41, 0xb8, 0x84, 0x57, 0xf0, 0xd8, 0x45, 0x6b, 0x90, 0x7d, 0xd2, 0xd9, 0xda, 0xd9, + 0xe6, 0xe1, 0x75, 0xf3, 0xf8, 0xa4, 0xbc, 0x14, 0x55, 0x4b, 0xa7, 0xa1, 0xd7, 0x21, 0xd3, 0xdf, + 0xee, 0x6e, 0xf4, 0xb4, 0x64, 0x09, 0x1d, 0x9f, 0x94, 0x17, 0xa3, 0x7a, 0xd1, 0xe7, 0xd2, 0x0d, + 0x35, 0xab, 0xf9, 0x48, 0xae, 0xff, 0x22, 0x09, 0x0b, 0x98, 0x93, 0x6d, 0x2f, 0xe8, 0x32, 0xc7, + 0x36, 0x27, 0xa8, 0x0b, 0x79, 0x93, 0xb9, 0x96, 0x1d, 0x5b, 0x53, 0xeb, 0x97, 0x1c, 0x8c, 0x53, + 0xad, 0xb0, 0x54, 0x0f, 0x35, 0xf1, 0xd4, 0x08, 0xfa, 0x00, 0x32, 0x16, 0x75, 0xc8, 0x44, 0x9d, + 0xd0, 0xb7, 0x2b, 0x92, 0xce, 0x57, 0x42, 0x3a, 0x5f, 0x69, 0x28, 0x3a, 0x8f, 0x25, 0x4e, 0x50, + 0x49, 0xf2, 0xcc, 0x20, 0x41, 0x40, 0x87, 0xa3, 0x40, 0x1e, 0xcf, 0x69, 0x5c, 0x18, 0x92, 0x67, + 0x55, 0x25, 0x42, 0xf7, 0x21, 0x7b, 0x64, 0xbb, 0x16, 0x3b, 0x52, 0x27, 0xf0, 0x15, 0x46, 0x15, + 0x50, 0x3f, 0xe6, 0xa7, 0xee, 0x85, 0x6e, 0x72, 0x7f, 0xb7, 0x3b, 0xed, 0x66, 0xe8, 0x6f, 0x55, + 0xdf, 0x71, 0xdb, 0xcc, 0xe5, 0x6b, 0x05, 0x3a, 0x6d, 0x63, 0xa3, 0xda, 0xda, 0xda, 0xc1, 0xdc, + 0xe7, 0xcb, 0xc7, 0x27, 0x65, 0x2d, 0x82, 0x6c, 0x10, 0xdb, 0xe1, 0x94, 0xf0, 0x36, 0xa4, 0xaa, + 0xed, 0x2f, 0xb5, 0x64, 0x49, 0x3b, 0x3e, 0x29, 0x17, 0xa3, 0xea, 0xaa, 0x3b, 0x99, 0x2e, 0xa3, + 0x8b, 0xed, 0xea, 0x7f, 0x93, 0x82, 0xe2, 0xce, 0xc8, 0x22, 0x01, 0x95, 0x31, 0x89, 0xca, 0x50, + 0x18, 0x11, 0x8f, 0x38, 0x0e, 0x75, 0x6c, 0x7f, 0xa8, 0x12, 0x95, 0xb8, 0x08, 0x7d, 0xfc, 0xb2, + 0x6e, 0xac, 0xe5, 0x78, 0x9c, 0xfd, 0xfe, 0x3f, 0xaf, 0x25, 0x42, 0x87, 0xee, 0xc0, 0xe2, 0x9e, + 0xec, 0xad, 0x41, 0x4c, 0x31, 0xb1, 0x29, 0x31, 0xb1, 0x95, 0x59, 0x13, 0x1b, 0xef, 0x56, 0x45, + 0x0d, 0xb2, 0x2a, 0xb4, 0xf0, 0xc2, 0x5e, 0xbc, 0x88, 0x1e, 0xc0, 0xfc, 0x90, 0xb9, 0x76, 0xc0, + 0xbc, 0xeb, 0x67, 0x21, 0x44, 0xa2, 0xf7, 0xe0, 0x06, 0x9f, 0xdc, 0xb0, 0x3f, 0xa2, 0x5a, 0x9c, + 0x58, 0x49, 0xbc, 0x34, 0x24, 0xcf, 0x54, 0x83, 0x98, 0x8b, 0x51, 0x0d, 0x32, 0xcc, 0xe3, 0x94, + 0x28, 0x2b, 0xba, 0xfb, 0xfe, 0xb5, 0xdd, 0x95, 0x85, 0x0e, 0xd7, 0xc1, 0x52, 0x55, 0xff, 0x08, + 0x16, 0xce, 0x0d, 0x82, 0x33, 0x81, 0x6e, 0x75, 0xa7, 0xd7, 0xd4, 0xe6, 0x50, 0x11, 0x72, 0xf5, + 0x4e, 0xbb, 0xdf, 0x6a, 0xef, 0x70, 0x2a, 0x53, 0x84, 0x1c, 0xee, 0x6c, 0x6d, 0xd5, 0xaa, 0xf5, + 0xc7, 0x5a, 0x52, 0xaf, 0x40, 0x21, 0x66, 0x0d, 0x2d, 0x02, 0xf4, 0xfa, 0x9d, 0xae, 0xb1, 0xd1, + 0xc2, 0xbd, 0xbe, 0x24, 0x42, 0xbd, 0x7e, 0x15, 0xf7, 0x95, 0x20, 0xa1, 0xff, 0x47, 0x32, 0x9c, + 0x51, 0xc5, 0x7d, 0x6a, 0xe7, 0xb9, 0xcf, 0x15, 0x9d, 0x57, 0xec, 0x67, 0x5a, 0x88, 0x38, 0xd0, + 0xc7, 0x00, 0x22, 0x70, 0xa8, 0x65, 0x90, 0x40, 0x4d, 0x7c, 0xe9, 0x05, 0x27, 0xf7, 0xc3, 0x7c, + 0x19, 0xe7, 0x15, 0xba, 0x1a, 0xa0, 0x1f, 0x43, 0xd1, 0x64, 0xc3, 0x91, 0x43, 0x95, 0x72, 0xea, + 0x5a, 0xe5, 0x42, 0x84, 0xaf, 0x06, 0x71, 0xf6, 0x95, 0x3e, 0xcf, 0x0f, 0x7f, 0x3b, 0x11, 0x7a, + 0x66, 0x06, 0xe1, 0x2a, 0x42, 0x6e, 0xa7, 0xdb, 0xa8, 0xf6, 0x5b, 0xed, 0x47, 0x5a, 0x02, 0x01, + 0x64, 0x85, 0xab, 0x1b, 0x5a, 0x92, 0x13, 0xc5, 0x7a, 0x67, 0xbb, 0xbb, 0xd5, 0x14, 0x94, 0x0b, + 0x2d, 0x83, 0x16, 0x3a, 0xdb, 0x10, 0x8e, 0x6c, 0x36, 0xb4, 0x34, 0xba, 0x09, 0x4b, 0x91, 0x54, + 0x69, 0x66, 0xd0, 0x2d, 0x40, 0x91, 0x70, 0x6a, 0x22, 0xab, 0xff, 0x06, 0x2c, 0xd5, 0x99, 0x1b, + 0x10, 0xdb, 0x8d, 0x48, 0xf4, 0x3a, 0x1f, 0xb4, 0x12, 0x19, 0xb6, 0x25, 0xf7, 0xf4, 0xda, 0xd2, + 0xd9, 0xe9, 0x5a, 0x21, 0x82, 0xb6, 0x1a, 0x7c, 0xa4, 0x61, 0xc1, 0xe2, 0xeb, 0x77, 0x64, 0x5b, + 0xc2, 0xb9, 0x99, 0xda, 0xfc, 0xd9, 0xe9, 0x5a, 0xaa, 0xdb, 0x6a, 0x60, 0x2e, 0x43, 0xaf, 0x41, + 0x9e, 0x3e, 0xb3, 0x03, 0xc3, 0xe4, 0x7b, 0x38, 0x77, 0x60, 0x06, 0xe7, 0xb8, 0xa0, 0xce, 0xb7, + 0xec, 0x1a, 0x40, 0x97, 0x79, 0x81, 0x6a, 0xf9, 0x47, 0x90, 0x19, 0x31, 0x4f, 0x64, 0xb0, 0x97, + 0xe6, 0xeb, 0x1c, 0x2e, 0x03, 0x15, 0x4b, 0xb0, 0xfe, 0x97, 0x49, 0x80, 0x3e, 0xf1, 0x0f, 0x94, + 0x91, 0x87, 0x90, 0x8f, 0xee, 0x3e, 0x54, 0x2a, 0x7c, 0xe5, 0x6c, 0x47, 0x60, 0xf4, 0x20, 0x0c, + 0x36, 0x99, 0x1e, 0xcc, 0x4c, 0x65, 0xc2, 0x86, 0x66, 0x31, 0xec, 0xf3, 0x39, 0x00, 0x3f, 0x12, + 0xa9, 0xe7, 0xa9, 0x99, 0xe7, 0x9f, 0xa8, 0x2e, 0x8e, 0x05, 0xe9, 0x34, 0x45, 0x30, 0xdf, 0x9c, + 0xd5, 0xc8, 0x85, 0x19, 0xd9, 0x9c, 0xc3, 0x53, 0x3d, 0xf4, 0x39, 0x14, 0xf8, 0xb8, 0x0d, 0x5f, + 0xd4, 0x29, 0x6e, 0x79, 0xa9, 0xab, 0xa4, 0x05, 0x0c, 0xa3, 0xe8, 0xbb, 0xa6, 0xc1, 0xa2, 0x37, + 0x76, 0xf9, 0xb0, 0x95, 0x0d, 0xdd, 0x86, 0x57, 0xdb, 0x34, 0x38, 0x62, 0xde, 0x41, 0x35, 0x08, + 0x88, 0xb9, 0x3f, 0xa4, 0xae, 0xf2, 0x71, 0x8c, 0x58, 0x27, 0xce, 0x11, 0xeb, 0x15, 0x98, 0x27, + 0x8e, 0x4d, 0x7c, 0x2a, 0xd9, 0x48, 0x1e, 0x87, 0x45, 0x4e, 0xff, 0x79, 0x32, 0x41, 0x7d, 0x9f, + 0xca, 0x14, 0x38, 0x8f, 0xa7, 0x02, 0xfd, 0xef, 0x93, 0x00, 0xad, 0x6e, 0x75, 0x5b, 0x99, 0x6f, + 0x40, 0x76, 0x8f, 0x0c, 0x6d, 0x67, 0x72, 0xd5, 0x02, 0x9f, 0xe2, 0x2b, 0x55, 0x69, 0x68, 0x43, + 0xe8, 0x60, 0xa5, 0x2b, 0xb2, 0x82, 0xf1, 0xae, 0x4b, 0x83, 0x28, 0x2b, 0x10, 0x25, 0x4e, 0x41, + 0x3c, 0xe2, 0x46, 0x33, 0x23, 0x0b, 0xbc, 0xeb, 0x03, 0x12, 0xd0, 0x23, 0x32, 0x09, 0x57, 0xa5, + 0x2a, 0xa2, 0x4d, 0x9e, 0x2d, 0xf8, 0xd4, 0x3b, 0xa4, 0xd6, 0x4a, 0x46, 0x84, 0xe0, 0x75, 0xfd, + 0xc1, 0x0a, 0x2e, 0xc9, 0x55, 0xa4, 0x5d, 0xfa, 0x54, 0x30, 0x82, 0x69, 0xd5, 0x77, 0x4a, 0xe0, + 0xef, 0xc1, 0xc2, 0xb9, 0x71, 0xbe, 0x90, 0x8e, 0xb5, 0xba, 0x4f, 0x7e, 0xa4, 0xa5, 0xd5, 0xd7, + 0x47, 0x5a, 0x56, 0xff, 0xe3, 0x94, 0x5c, 0x47, 0xca, 0xab, 0xb3, 0xaf, 0xd4, 0x72, 0x22, 0xfa, + 0x4d, 0xe6, 0xa8, 0xf8, 0x7e, 0xfb, 0xea, 0xe5, 0xc5, 0xe9, 0xbd, 0x80, 0xe3, 0x48, 0x11, 0xad, + 0x41, 0x41, 0xce, 0xbf, 0xc1, 0xe3, 0x49, 0xb8, 0x75, 0x01, 0x83, 0x14, 0x71, 0x4d, 0x74, 0x17, + 0x16, 0x47, 0xe3, 0x5d, 0xc7, 0xf6, 0xf7, 0xa9, 0x25, 0x31, 0x69, 0x81, 0x59, 0x88, 0xa4, 0x02, + 0xb6, 0x0d, 0x45, 0x25, 0x30, 0x04, 0xb5, 0xcb, 0x88, 0x0e, 0xbd, 0x77, 0x5d, 0x87, 0xa4, 0x8a, + 0x60, 0x7c, 0x85, 0xd1, 0xb4, 0xa0, 0x37, 0x20, 0x17, 0x76, 0x16, 0xad, 0x40, 0xaa, 0x5f, 0xef, + 0x6a, 0x73, 0xa5, 0xa5, 0xe3, 0x93, 0x72, 0x21, 0x14, 0xf7, 0xeb, 0x5d, 0x5e, 0xb3, 0xd3, 0xe8, + 0x6a, 0x89, 0xf3, 0x35, 0x3b, 0x8d, 0x6e, 0x29, 0xcd, 0x29, 0x86, 0xbe, 0x07, 0x85, 0x58, 0x0b, + 0xe8, 0x4d, 0x98, 0x6f, 0xb5, 0x1f, 0xe1, 0x66, 0xaf, 0xa7, 0xcd, 0x95, 0x6e, 0x1d, 0x9f, 0x94, + 0x51, 0xac, 0xb6, 0xe5, 0x0e, 0xf8, 0xfc, 0xa0, 0xd7, 0x21, 0xbd, 0xd9, 0xe1, 0x47, 0x97, 0xe4, + 0x92, 0x31, 0xc4, 0x26, 0xf3, 0x83, 0xd2, 0x4d, 0xc5, 0x5d, 0xe2, 0x86, 0xf5, 0x3f, 0x48, 0x40, + 0x56, 0x52, 0xea, 0x99, 0x13, 0x55, 0x85, 0xf9, 0x30, 0xd1, 0x93, 0x3c, 0xff, 0xed, 0xcb, 0x39, + 0x79, 0x45, 0x51, 0x68, 0x19, 0x7e, 0xa1, 0x5e, 0xe9, 0x13, 0x28, 0xc6, 0x2b, 0xbe, 0x53, 0xf0, + 0xfd, 0x3a, 0x14, 0x78, 0x7c, 0x87, 0xdc, 0x7c, 0x1d, 0xb2, 0x92, 0xf6, 0x47, 0x5b, 0xe9, 0xe5, + 0x09, 0x82, 0x42, 0xa2, 0x87, 0x30, 0x2f, 0x93, 0x8a, 0xf0, 0x0a, 0x6c, 0xf5, 0xea, 0x55, 0x84, + 0x43, 0xb8, 0xfe, 0x39, 0xa4, 0xbb, 0x94, 0x7a, 0xdc, 0xf7, 0x2e, 0xb3, 0xe8, 0xf4, 0xf4, 0x51, + 0xf9, 0x90, 0x45, 0x5b, 0x0d, 0x9e, 0x0f, 0x59, 0xb4, 0x65, 0x45, 0x37, 0x18, 0xc9, 0xd8, 0x0d, + 0x46, 0x1f, 0x8a, 0x4f, 0xa9, 0x3d, 0xd8, 0x0f, 0xa8, 0x25, 0x0c, 0xbd, 0x0f, 0xe9, 0x11, 0x8d, + 0x3a, 0xbf, 0x32, 0x33, 0xc0, 0x28, 0xf5, 0xb0, 0x40, 0xf1, 0x7d, 0xe4, 0x48, 0x68, 0xab, 0x8b, + 0x57, 0x55, 0xd2, 0xff, 0x2e, 0x09, 0x8b, 0x2d, 0xdf, 0x1f, 0x13, 0xd7, 0x0c, 0x89, 0xc9, 0x67, + 0xe7, 0x89, 0xc9, 0x3b, 0x33, 0x47, 0x78, 0x4e, 0xe5, 0xfc, 0xc5, 0x8c, 0x3a, 0x1c, 0x92, 0xd1, + 0xe1, 0xa0, 0xff, 0x7b, 0x22, 0xbc, 0x7d, 0xb9, 0x1b, 0x5b, 0xee, 0xa5, 0x95, 0xe3, 0x93, 0xf2, + 0x72, 0xdc, 0x12, 0xdd, 0x71, 0x0f, 0x5c, 0x76, 0xe4, 0xa2, 0x37, 0x20, 0x83, 0x9b, 0xed, 0xe6, + 0x53, 0x2d, 0x21, 0xc3, 0xf3, 0x1c, 0x08, 0x53, 0x97, 0x1e, 0x71, 0x4b, 0xdd, 0x66, 0xbb, 0xc1, + 0x89, 0x44, 0x72, 0x86, 0xa5, 0x2e, 0x75, 0x2d, 0xdb, 0x1d, 0xa0, 0x37, 0x21, 0xdb, 0xea, 0xf5, + 0x76, 0x44, 0x7e, 0xfc, 0xea, 0xf1, 0x49, 0xf9, 0xe6, 0x39, 0x14, 0x2f, 0x50, 0x8b, 0x83, 0x38, + 0x8b, 0xe7, 0x14, 0x63, 0x06, 0x88, 0xd3, 0x43, 0x09, 0xc2, 0x9d, 0x3e, 0x4f, 0xde, 0x33, 0x33, + 0x40, 0x98, 0xf1, 0xbf, 0x6a, 0xb9, 0xfd, 0x53, 0x12, 0xb4, 0xaa, 0x69, 0xd2, 0x51, 0xc0, 0xeb, + 0x55, 0xe2, 0xd4, 0x87, 0xdc, 0x88, 0x7f, 0xd9, 0x34, 0x24, 0x01, 0x0f, 0x67, 0x5e, 0xfd, 0x5f, + 0xd0, 0xab, 0x60, 0xe6, 0xd0, 0xaa, 0x35, 0xb4, 0x7d, 0xdf, 0x66, 0xae, 0x94, 0xe1, 0xc8, 0x52, + 0xe9, 0x3f, 0x13, 0x70, 0x73, 0x06, 0x02, 0xdd, 0x83, 0xb4, 0xc7, 0x9c, 0x70, 0x0e, 0xef, 0x5c, + 0x76, 0xb1, 0xc6, 0x55, 0xb1, 0x40, 0xa2, 0x55, 0x00, 0x32, 0x0e, 0x18, 0x11, 0xed, 0x8b, 0xd9, + 0xcb, 0xe1, 0x98, 0x04, 0x3d, 0x85, 0xac, 0x4f, 0x4d, 0x8f, 0x86, 0x54, 0xf1, 0xf3, 0xff, 0x6b, + 0xef, 0x2b, 0x3d, 0x61, 0x06, 0x2b, 0x73, 0xa5, 0x0a, 0x64, 0xa5, 0x84, 0x87, 0xbd, 0x45, 0x02, + 0x22, 0x3a, 0x5d, 0xc4, 0xe2, 0x9b, 0x47, 0x13, 0x71, 0x06, 0x61, 0x34, 0x11, 0x67, 0xa0, 0xff, + 0x75, 0x12, 0xa0, 0xf9, 0x2c, 0xa0, 0x9e, 0x4b, 0x9c, 0x7a, 0x15, 0x35, 0x63, 0xbb, 0xbf, 0x1c, + 0xed, 0xbb, 0x33, 0xaf, 0x5b, 0x23, 0x8d, 0x4a, 0xbd, 0x3a, 0x63, 0xff, 0xbf, 0x0d, 0xa9, 0xb1, + 0xa7, 0x5e, 0x73, 0x24, 0xcd, 0xdb, 0xc1, 0x5b, 0x98, 0xcb, 0x50, 0x73, 0xba, 0x6d, 0xa5, 0x2e, + 0x7f, 0xb3, 0x89, 0x35, 0x30, 0x73, 0xeb, 0xe2, 0x2b, 0xdf, 0x24, 0x86, 0x49, 0xd5, 0xc9, 0x51, + 0x94, 0x2b, 0xbf, 0x5e, 0xad, 0x53, 0x2f, 0xc0, 0x59, 0x93, 0xf0, 0xff, 0xdf, 0x6b, 0x7f, 0x7b, + 0x1f, 0x60, 0x3a, 0x34, 0xb4, 0x0a, 0x99, 0xfa, 0x46, 0xaf, 0xb7, 0xa5, 0xcd, 0xc9, 0x0d, 0x7c, + 0x5a, 0x25, 0xc4, 0xfa, 0x9f, 0x27, 0x21, 0x57, 0xaf, 0xaa, 0x63, 0xb5, 0x0e, 0x9a, 0xd8, 0x95, + 0x78, 0xef, 0x0c, 0xfa, 0x6c, 0x64, 0x7b, 0x13, 0xb5, 0xb1, 0x5c, 0x91, 0xb3, 0x2d, 0x72, 0x15, + 0xde, 0xeb, 0xa6, 0x50, 0x40, 0x18, 0x8a, 0x54, 0x39, 0xc1, 0x30, 0x49, 0xb8, 0xc7, 0xaf, 0x5e, + 0xed, 0x2c, 0xc9, 0xbe, 0xa7, 0x65, 0x1f, 0x17, 0x42, 0x23, 0x75, 0xe2, 0xa3, 0x8f, 0x61, 0xc9, + 0xb7, 0x07, 0xae, 0xed, 0x0e, 0x8c, 0xd0, 0x79, 0x29, 0xe1, 0xbc, 0x1b, 0x67, 0xa7, 0x6b, 0x0b, + 0x3d, 0x59, 0xa5, 0x7c, 0xb8, 0xa0, 0x90, 0x75, 0xe1, 0x4a, 0xf4, 0x11, 0x2c, 0xc6, 0x54, 0xb9, + 0x17, 0xa5, 0xdb, 0xb5, 0xb3, 0xd3, 0xb5, 0x62, 0xa4, 0xf9, 0x98, 0x4e, 0x70, 0x31, 0x52, 0x7c, + 0x4c, 0xc5, 0xf5, 0xc2, 0x1e, 0xf3, 0x4c, 0x6a, 0x78, 0x62, 0x4d, 0x8b, 0x13, 0x3c, 0x8d, 0x0b, + 0x42, 0x26, 0x97, 0xb9, 0xfe, 0x04, 0x6e, 0x76, 0x3c, 0x73, 0x9f, 0xfa, 0x81, 0x74, 0x85, 0xf2, + 0xe2, 0xe7, 0x70, 0x27, 0x20, 0xfe, 0x81, 0xb1, 0x6f, 0xfb, 0x01, 0xf3, 0x26, 0x86, 0x47, 0x03, + 0xea, 0xf2, 0x7a, 0x43, 0xbc, 0x48, 0xa9, 0xfb, 0x9f, 0xdb, 0x1c, 0xb3, 0x29, 0x21, 0x38, 0x44, + 0x6c, 0x71, 0x80, 0xde, 0x82, 0x22, 0x67, 0xe1, 0x0d, 0xba, 0x47, 0xc6, 0x4e, 0xc0, 0x47, 0x0f, + 0x0e, 0x1b, 0x18, 0x2f, 0x7d, 0x4c, 0xe5, 0x1d, 0x36, 0x90, 0x9f, 0xfa, 0x4f, 0x41, 0x6b, 0xd8, + 0xfe, 0x88, 0x04, 0xe6, 0x7e, 0x78, 0xb1, 0x85, 0x1a, 0xa0, 0xed, 0x53, 0xe2, 0x05, 0xbb, 0x94, + 0x04, 0xc6, 0x88, 0x7a, 0x36, 0xb3, 0xae, 0x9f, 0xe5, 0xa5, 0x48, 0xa5, 0x2b, 0x34, 0xf4, 0xff, + 0x4a, 0x00, 0x60, 0xb2, 0x17, 0x32, 0xb2, 0x1f, 0xc2, 0x0d, 0xdf, 0x25, 0x23, 0x7f, 0x9f, 0x05, + 0x86, 0xed, 0x06, 0xd4, 0x3b, 0x24, 0x8e, 0xba, 0x9f, 0xd0, 0xc2, 0x8a, 0x96, 0x92, 0xa3, 0xf7, + 0x01, 0x1d, 0x50, 0x3a, 0x32, 0x98, 0x63, 0x19, 0x61, 0xa5, 0x7c, 0x2f, 0x4b, 0x63, 0x8d, 0xd7, + 0x74, 0x1c, 0xab, 0x17, 0xca, 0x51, 0x0d, 0x56, 0xf9, 0xf0, 0xa9, 0x1b, 0x78, 0x36, 0xf5, 0x8d, + 0x3d, 0xe6, 0x19, 0xbe, 0xc3, 0x8e, 0x8c, 0x3d, 0xe6, 0x38, 0xec, 0x88, 0x7a, 0xe1, 0xd5, 0x4f, + 0xc9, 0x61, 0x83, 0xa6, 0x04, 0x6d, 0x30, 0xaf, 0xe7, 0xb0, 0xa3, 0x8d, 0x10, 0xc1, 0x69, 0xdb, + 0x74, 0xcc, 0x81, 0x6d, 0x1e, 0x84, 0xb4, 0x2d, 0x92, 0xf6, 0x6d, 0xf3, 0x00, 0xbd, 0x09, 0x0b, + 0xd4, 0xa1, 0xe2, 0x06, 0x40, 0xa2, 0x32, 0x02, 0x55, 0x0c, 0x85, 0x1c, 0xa4, 0x7f, 0x01, 0x5a, + 0xd3, 0x35, 0xbd, 0xc9, 0x28, 0x36, 0xe7, 0xef, 0x03, 0xe2, 0x9b, 0xa4, 0xe1, 0x30, 0xf3, 0xc0, + 0x18, 0x12, 0x97, 0x0c, 0x78, 0xbf, 0xe4, 0x1b, 0x8d, 0xc6, 0x6b, 0xb6, 0x98, 0x79, 0xb0, 0xad, + 0xe4, 0xfa, 0xc7, 0x00, 0xbd, 0x91, 0x47, 0x89, 0xd5, 0xe1, 0x6c, 0x82, 0xbb, 0x4e, 0x94, 0x0c, + 0x4b, 0x3d, 0x03, 0x31, 0x4f, 0x2d, 0x75, 0x4d, 0x56, 0x34, 0x22, 0xb9, 0xfe, 0xcb, 0x70, 0xb3, + 0xeb, 0x10, 0x53, 0x3c, 0x89, 0x76, 0xa3, 0x47, 0x07, 0xf4, 0x10, 0xb2, 0x12, 0xaa, 0x66, 0x72, + 0xe6, 0x72, 0x9b, 0xb6, 0xb9, 0x39, 0x87, 0x15, 0xbe, 0x56, 0x04, 0x98, 0xda, 0xd1, 0x9f, 0x41, + 0x3e, 0x32, 0x8f, 0xca, 0xc0, 0x53, 0x60, 0x1e, 0xdd, 0xb6, 0xab, 0x72, 0xd6, 0x3c, 0x8e, 0x8b, + 0x50, 0x0b, 0x0a, 0xa3, 0x48, 0xf9, 0x4a, 0x3a, 0x37, 0xa3, 0xd3, 0x38, 0xae, 0xab, 0x7f, 0x06, + 0xf0, 0x13, 0x66, 0xbb, 0x7d, 0x76, 0x40, 0x5d, 0xf1, 0xce, 0xc5, 0xb3, 0x35, 0x1a, 0x3a, 0x42, + 0x95, 0x44, 0x32, 0x2a, 0xbd, 0x18, 0x3d, 0xf7, 0xc8, 0xa2, 0xfe, 0x57, 0x49, 0xc8, 0x62, 0xc6, + 0x82, 0x7a, 0x15, 0x95, 0x21, 0xab, 0x96, 0xba, 0x38, 0x42, 0x6a, 0xf9, 0xb3, 0xd3, 0xb5, 0x8c, + 0x5c, 0xe3, 0x19, 0x53, 0x2c, 0xee, 0xd8, 0x26, 0x9c, 0xbc, 0x6c, 0x13, 0x46, 0xf7, 0xa0, 0xa8, + 0x40, 0xc6, 0x3e, 0xf1, 0xf7, 0x65, 0x8e, 0x55, 0x5b, 0x3c, 0x3b, 0x5d, 0x03, 0x89, 0xdc, 0x24, + 0xfe, 0x3e, 0x06, 0x89, 0xe6, 0xdf, 0xa8, 0x09, 0x85, 0xaf, 0x98, 0xed, 0x1a, 0x81, 0x18, 0x84, + 0xba, 0xee, 0x9a, 0x39, 0x15, 0xd3, 0xa1, 0xaa, 0x77, 0x51, 0xf8, 0x6a, 0x3a, 0xf8, 0x26, 0x2c, + 0x78, 0x8c, 0x05, 0x72, 0xe7, 0xb1, 0x99, 0xab, 0x32, 0xe9, 0xf2, 0xcc, 0x0b, 0x56, 0xc6, 0x02, + 0xac, 0x70, 0xb8, 0xe8, 0xc5, 0x4a, 0xe8, 0x1e, 0x2c, 0x3b, 0xc4, 0x0f, 0x0c, 0xb1, 0x65, 0x59, + 0x53, 0x6b, 0x59, 0xb1, 0x5a, 0x10, 0xaf, 0xdb, 0x10, 0x55, 0xa1, 0x86, 0xfe, 0x0f, 0x09, 0x28, + 0xf0, 0xc1, 0xd8, 0x7b, 0xb6, 0xc9, 0x79, 0xda, 0x77, 0xa7, 0x0f, 0xb7, 0x21, 0x65, 0xfa, 0x9e, + 0x72, 0xaa, 0x38, 0x3f, 0xeb, 0x3d, 0x8c, 0xb9, 0x0c, 0x7d, 0x01, 0x59, 0x95, 0xd1, 0x4b, 0xe6, + 0xa0, 0x5f, 0xcf, 0x28, 0x95, 0x6f, 0x94, 0x9e, 0x88, 0xc7, 0x69, 0xef, 0xe4, 0x3e, 0x8e, 0xe3, + 0x22, 0x74, 0x0b, 0x92, 0xa6, 0x74, 0x97, 0x7a, 0x78, 0xaf, 0xb7, 0x71, 0xd2, 0x74, 0xf5, 0xbf, + 0x4d, 0xc0, 0xc2, 0x74, 0xcd, 0xf2, 0x08, 0xb8, 0x03, 0x79, 0x7f, 0xbc, 0xeb, 0x4f, 0xfc, 0x80, + 0x0e, 0xc3, 0x37, 0xbc, 0x48, 0x80, 0x5a, 0x90, 0x27, 0xce, 0x80, 0x79, 0x76, 0xb0, 0x3f, 0x54, + 0xc9, 0xe4, 0xec, 0xd3, 0x3e, 0x6e, 0xb3, 0x52, 0x0d, 0x55, 0xf0, 0x54, 0x3b, 0x3c, 0xba, 0xc5, + 0x71, 0x25, 0x8f, 0xee, 0x37, 0xa0, 0xe8, 0x90, 0xa1, 0xb8, 0xe2, 0x08, 0xec, 0xa1, 0x1c, 0x47, + 0x1a, 0x17, 0x94, 0xac, 0x6f, 0x0f, 0xa9, 0xae, 0x43, 0x3e, 0x32, 0x86, 0x96, 0xa0, 0x50, 0x6d, + 0xf6, 0x8c, 0xfb, 0xeb, 0x0f, 0x8d, 0x47, 0xf5, 0x6d, 0x6d, 0x4e, 0xd1, 0xcb, 0x3f, 0x4b, 0xc0, + 0x82, 0xda, 0x51, 0x14, 0x65, 0x7f, 0x13, 0xe6, 0x3d, 0xb2, 0x17, 0x84, 0x49, 0x45, 0x5a, 0x46, + 0x35, 0xdf, 0xa4, 0x79, 0x52, 0xc1, 0xab, 0x66, 0x27, 0x15, 0xb1, 0x57, 0xe5, 0xd4, 0x95, 0xaf, + 0xca, 0xe9, 0xff, 0x97, 0x57, 0x65, 0xfd, 0x4f, 0x92, 0xb0, 0xa4, 0xd8, 0x5f, 0xb4, 0x81, 0xbd, + 0x0b, 0x79, 0x49, 0x04, 0xa7, 0x29, 0x91, 0x78, 0xc8, 0x94, 0xb8, 0x56, 0x03, 0xe7, 0x64, 0x75, + 0xcb, 0xe2, 0x39, 0xba, 0x82, 0xc6, 0x7e, 0x23, 0x01, 0x52, 0xd4, 0xe6, 0x09, 0x66, 0x03, 0xd2, + 0x7b, 0xb6, 0x43, 0x55, 0x9c, 0xcd, 0xbc, 0xbe, 0xbe, 0xd0, 0xbc, 0x78, 0x68, 0xe9, 0x8b, 0x2c, + 0x7f, 0x73, 0x0e, 0x0b, 0xed, 0xd2, 0x6f, 0x02, 0x4c, 0xa5, 0x33, 0x13, 0x59, 0x4e, 0x16, 0xd5, + 0x9d, 0x60, 0x48, 0x16, 0x5b, 0x0d, 0xcc, 0x65, 0xbc, 0x6a, 0x60, 0x5b, 0x6a, 0xcb, 0x10, 0x55, + 0x8f, 0x78, 0xd5, 0xc0, 0xb6, 0xa2, 0xd7, 0x9e, 0xf4, 0x35, 0xaf, 0x3d, 0xb5, 0x5c, 0x78, 0x33, + 0xa5, 0x6f, 0xc1, 0xad, 0x9a, 0x43, 0xcc, 0x03, 0xc7, 0xf6, 0x03, 0x6a, 0xc5, 0x57, 0xe8, 0x3a, + 0x64, 0xcf, 0xf1, 0xb4, 0xab, 0x2e, 0x02, 0x15, 0x52, 0xff, 0xb7, 0x04, 0x14, 0x37, 0x29, 0x71, + 0x82, 0xfd, 0xe9, 0x6d, 0x4a, 0x40, 0xfd, 0x40, 0x6d, 0xf0, 0xe2, 0x1b, 0x7d, 0x08, 0xb9, 0xe8, + 0x18, 0xbf, 0xf6, 0x45, 0x26, 0x82, 0xa2, 0x07, 0x30, 0xcf, 0x63, 0x9a, 0x8d, 0xc3, 0xfc, 0xe0, + 0xaa, 0xcb, 0x7e, 0x85, 0xe4, 0x9b, 0xba, 0x47, 0xc5, 0xb9, 0x2d, 0x9c, 0x92, 0xc1, 0x61, 0x11, + 0xfd, 0x12, 0x14, 0xc5, 0x5d, 0x75, 0x48, 0x53, 0x32, 0xd7, 0xd9, 0x2c, 0xc8, 0xe7, 0x26, 0x49, + 0x51, 0xfe, 0x27, 0x01, 0xcb, 0xdb, 0x64, 0xb2, 0x4b, 0xd5, 0x32, 0xa5, 0x16, 0xa6, 0x26, 0xf3, + 0x2c, 0xd4, 0x8d, 0x2f, 0xef, 0x2b, 0x5e, 0xaf, 0x66, 0x29, 0xcf, 0x5e, 0xe5, 0x61, 0xce, 0x92, + 0x8c, 0xe5, 0x2c, 0xcb, 0x90, 0x71, 0x99, 0x6b, 0x52, 0xb5, 0xf6, 0x65, 0x41, 0xb7, 0xe3, 0x4b, + 0xbb, 0x14, 0x3d, 0x2c, 0x89, 0x67, 0xa1, 0x36, 0x0b, 0xa2, 0xd6, 0xd0, 0x17, 0x50, 0xea, 0x35, + 0xeb, 0xb8, 0xd9, 0xaf, 0x75, 0x7e, 0x6a, 0xf4, 0xaa, 0x5b, 0xbd, 0xea, 0xfa, 0x3d, 0xa3, 0xdb, + 0xd9, 0xfa, 0xf2, 0xfe, 0x83, 0x7b, 0x1f, 0x6a, 0x89, 0x52, 0xf9, 0xf8, 0xa4, 0x7c, 0xa7, 0x5d, + 0xad, 0x6f, 0xc9, 0x58, 0xde, 0x65, 0xcf, 0x7a, 0xc4, 0xf1, 0xc9, 0xfa, 0xbd, 0x2e, 0x73, 0x26, + 0x1c, 0xa3, 0x9f, 0x24, 0xa0, 0x18, 0x3f, 0x1f, 0xe2, 0xc7, 0x5e, 0xe2, 0xd2, 0x63, 0x6f, 0x7a, + 0x7a, 0x26, 0x2f, 0x39, 0x3d, 0x37, 0x60, 0xd9, 0xf4, 0x98, 0xef, 0x1b, 0x9c, 0x30, 0x53, 0xeb, + 0x02, 0x25, 0x7f, 0xe5, 0xec, 0x74, 0xed, 0x46, 0x9d, 0xd7, 0xf7, 0x44, 0xb5, 0x32, 0x7f, 0xc3, + 0x8c, 0x89, 0x44, 0x4b, 0xef, 0xfd, 0x22, 0x05, 0xf9, 0xe8, 0xba, 0x99, 0x2f, 0x19, 0x9e, 0xeb, + 0x2b, 0x57, 0x44, 0xf2, 0x36, 0x3d, 0x42, 0x6f, 0x4c, 0xb3, 0xfc, 0x2f, 0xe4, 0xfb, 0x5a, 0x54, + 0x1d, 0x66, 0xf8, 0x6f, 0x41, 0xae, 0xda, 0xeb, 0xb5, 0x1e, 0xb5, 0x9b, 0x0d, 0xed, 0xeb, 0x44, + 0xe9, 0x95, 0xe3, 0x93, 0xf2, 0x8d, 0x08, 0x54, 0xf5, 0x65, 0x4f, 0x05, 0xaa, 0x5e, 0x6f, 0x76, + 0xfb, 0xcd, 0x86, 0xf6, 0x3c, 0x79, 0x11, 0x25, 0xb2, 0x56, 0xf1, 0x4a, 0x9e, 0xef, 0xe2, 0x66, + 0xb7, 0x8a, 0x79, 0x83, 0x5f, 0x27, 0xe5, 0xe5, 0xc3, 0xb4, 0x45, 0x8f, 0x8e, 0x88, 0xc7, 0xdb, + 0x5c, 0x0d, 0x7f, 0x2d, 0xf2, 0x3c, 0x25, 0x5f, 0x52, 0xa7, 0x77, 0xe7, 0x94, 0x58, 0x13, 0xde, + 0x9a, 0x78, 0xb4, 0x10, 0x66, 0x52, 0x17, 0x5a, 0xeb, 0xf1, 0x40, 0xe5, 0x56, 0x74, 0x98, 0xc7, + 0x3b, 0xed, 0x36, 0x07, 0x3d, 0x4f, 0x5f, 0x18, 0x1d, 0x1e, 0xbb, 0x3c, 0x23, 0x41, 0x77, 0x21, + 0x17, 0xbe, 0x69, 0x68, 0x5f, 0xa7, 0x2f, 0x74, 0xa8, 0x1e, 0x3e, 0xc8, 0x88, 0x06, 0x37, 0x77, + 0xfa, 0xe2, 0xc7, 0x2c, 0xcf, 0x33, 0x17, 0x1b, 0xdc, 0x1f, 0x07, 0x16, 0x3b, 0x72, 0xf9, 0x04, + 0xab, 0x7b, 0x8e, 0xaf, 0x33, 0x32, 0x29, 0x8c, 0x30, 0xea, 0x92, 0xe3, 0x2d, 0xc8, 0xe1, 0xe6, + 0x4f, 0xe4, 0xef, 0x5e, 0x9e, 0x67, 0x2f, 0xd8, 0xc1, 0xf4, 0x2b, 0x6a, 0xaa, 0xd6, 0x3a, 0xb8, + 0xbb, 0x59, 0x15, 0x2e, 0xbf, 0x88, 0xea, 0x78, 0xa3, 0x7d, 0xe2, 0x52, 0x6b, 0xfa, 0x9c, 0x1c, + 0x55, 0xbd, 0xf7, 0x2b, 0x90, 0x0b, 0x69, 0x03, 0x5a, 0x85, 0xec, 0xd3, 0x0e, 0x7e, 0xdc, 0xc4, + 0xda, 0x9c, 0xf4, 0x61, 0x58, 0xf3, 0x54, 0x12, 0xbe, 0x32, 0xcc, 0x6f, 0x57, 0xdb, 0xd5, 0x47, + 0x4d, 0x1c, 0x5e, 0x41, 0x86, 0x00, 0x75, 0xf6, 0x95, 0x34, 0xd5, 0x40, 0x64, 0xb3, 0xb6, 0xf2, + 0xcd, 0xb7, 0xab, 0x73, 0x3f, 0xff, 0x76, 0x75, 0xee, 0xf9, 0xd9, 0x6a, 0xe2, 0x9b, 0xb3, 0xd5, + 0xc4, 0xcf, 0xce, 0x56, 0x13, 0xff, 0x72, 0xb6, 0x9a, 0xd8, 0xcd, 0x8a, 0x1d, 0xe3, 0xc1, 0xff, + 0x06, 0x00, 0x00, 0xff, 0xff, 0x45, 0x07, 0xa3, 0xee, 0x6e, 0x2a, 0x00, 0x00, } diff --git a/api/types.proto b/api/types.proto index 9e63e67dc7..8420ad4839 100644 --- a/api/types.proto +++ b/api/types.proto @@ -668,6 +668,21 @@ message CAConfig { // ExternalCAs is a list of CAs to which a manager node will make // certificate signing requests for node certificates. repeated ExternalCA external_cas = 2 [(gogoproto.customname) = "ExternalCAs"]; + + // SigningCACert is the desired CA certificate to be used as the root and + // signing CA for the swarm. If not provided, indicates that we are either happy + // with the current configuration, or (together with a bump in the ForceRotate value) + // that we want a certificate and key generated for us. + bytes signing_ca_cert = 3 [(gogoproto.customname) = "SigningCACert"]; + + // SigningCAKey is the desired private key, matching the signing CA cert, to be used + // to sign certificates for the swarm + bytes signing_ca_key = 4 [(gogoproto.customname) = "SigningCAKey"]; + + // ForceRotate is a counter that triggers a root CA rotation even if no relevant + // parameters have been in the spec. This will force the manager to generate a new + // certificate and key, if none have been provided. + uint64 force_rotate = 5; } // OrchestrationConfig defines cluster-level orchestration settings. @@ -771,6 +786,10 @@ message RootCA { // RootRotation contains the new root cert and key we want to rotate to - if this is nil, we are not in the // middle of a root rotation RootRotation root_rotation = 5; + + // LastForcedRotation matches the Cluster Spec's CAConfig's ForceRotation counter. + // It indicates when the current CA cert and key were generated (or updated). + uint64 last_forced_rotation = 6; } @@ -922,5 +941,5 @@ message RootRotation { bytes ca_cert = 1 [(gogoproto.customname) = "CACert"]; bytes ca_key = 2 [(gogoproto.customname) = "CAKey"]; // cross-signed CA cert is the CACert that has been cross-signed by the previous root - bytes cross_signed_ca_cert = 3 [(gogoproto.customname) = "CrossSignedCACert"];; + bytes cross_signed_ca_cert = 3 [(gogoproto.customname) = "CrossSignedCACert"]; } From 62b9dac2b3439bba5ac6ff938907f8dd24081b84 Mon Sep 17 00:00:00 2001 From: cyli Date: Tue, 28 Mar 2017 17:35:30 -0700 Subject: [PATCH 2/3] Provide utility to validate any CAConfig spec changes during a swarm update, which ensures that 1. A minimum of required external CAs are provided and reachable and valid. 2. If a desired signing certificate and/or signing key are provided, that they are valid. If the CAConfig spec differs from the current root CA + current root rotation configuration, this function will also start, update, or abort a root rotation. It returns a new api.RootCA object (which may be completely unchanged from the current one) to replace the existing api.RootCA object in the cluster. Signed-off-by: cyli --- ca/external.go | 13 + ca/external_test.go | 29 ++ ca/testutils/cautils.go | 12 + manager/controlapi/ca_rotation.go | 265 ++++++++++ manager/controlapi/ca_rotation_test.go | 671 +++++++++++++++++++++++++ 5 files changed, 990 insertions(+) create mode 100644 manager/controlapi/ca_rotation.go create mode 100644 manager/controlapi/ca_rotation_test.go diff --git a/ca/external.go b/ca/external.go index 44607176bd..6f23ff1fbf 100644 --- a/ca/external.go +++ b/ca/external.go @@ -53,6 +53,19 @@ func NewExternalCA(rootCA *RootCA, tlsConfig *tls.Config, urls ...string) *Exter } } +// Copy returns a copy of the external CA that can be updated independently +func (eca *ExternalCA) Copy() *ExternalCA { + eca.mu.Lock() + defer eca.mu.Unlock() + + return &ExternalCA{ + ExternalRequestTimeout: eca.ExternalRequestTimeout, + rootCA: eca.rootCA, + urls: eca.urls, + client: eca.client, + } +} + // UpdateTLSConfig updates the HTTP Client for this ExternalCA by creating // a new client which uses the given tlsConfig. func (eca *ExternalCA) UpdateTLSConfig(tlsConfig *tls.Config) { diff --git a/ca/external_test.go b/ca/external_test.go index 8e3ae6d3fc..0efa485077 100644 --- a/ca/external_test.go +++ b/ca/external_test.go @@ -126,3 +126,32 @@ func TestExternalCASignRequestTimesOut(t *testing.T) { require.FailNow(t, "call to external CA signing should have timed out after 1 second - it's been 3") } } + +func TestExternalCACopy(t *testing.T) { + t.Parallel() + + if !testutils.External { + return // this is only tested using the external CA + } + + tc := testutils.NewTestCA(t) + defer tc.Stop() + + csr, _, err := ca.GenerateNewCSR() + require.NoError(t, err) + signReq := ca.PrepareCSR(csr, "cn", ca.ManagerRole, tc.Organization) + + secConfig, err := tc.NewNodeConfig(ca.ManagerRole) + require.NoError(t, err) + externalCA1 := secConfig.ExternalCA() + externalCA2 := externalCA1.Copy() + externalCA2.UpdateURLs(tc.ExternalSigningServer.URL) + + // externalCA1 can't sign, but externalCA2, which has been updated with URLS, can + _, err = externalCA1.Sign(context.Background(), signReq) + require.Equal(t, ca.ErrNoExternalCAURLs, err) + + cert, err := externalCA2.Sign(context.Background(), signReq) + require.NoError(t, err) + require.NotNil(t, cert) +} diff --git a/ca/testutils/cautils.go b/ca/testutils/cautils.go index 8cedbd4685..88f463e182 100644 --- a/ca/testutils/cautils.go +++ b/ca/testutils/cautils.go @@ -1,6 +1,7 @@ package testutils import ( + "crypto" cryptorand "crypto/rand" "crypto/tls" "crypto/x509" @@ -418,3 +419,14 @@ func ReDateCert(t *testing.T, cert, signerCert, signerKey []byte, notBefore, not Bytes: derBytes, }) } + +// CreateCertFromSigner creates a Certificate authority for a new Swarm Cluster given an existing key only. +func CreateCertFromSigner(rootCN string, priv crypto.Signer) ([]byte, error) { + req := cfcsr.CertificateRequest{ + CN: rootCN, + KeyRequest: &cfcsr.BasicKeyRequest{A: ca.RootKeyAlgo, S: ca.RootKeySize}, + CA: &cfcsr.CAConfig{Expiry: ca.RootCAExpiration}, + } + cert, _, err := initca.NewFromSigner(&req, priv) + return cert, err +} diff --git a/manager/controlapi/ca_rotation.go b/manager/controlapi/ca_rotation.go new file mode 100644 index 0000000000..5e9e83e859 --- /dev/null +++ b/manager/controlapi/ca_rotation.go @@ -0,0 +1,265 @@ +package controlapi + +import ( + "bytes" + "context" + "crypto/tls" + "crypto/x509" + "errors" + "net" + "net/url" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + "github.com/cloudflare/cfssl/helpers" + "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/ca" + "github.com/docker/swarmkit/log" +) + +var minRootExpiration = 1 * helpers.OneYear + +// determines whether an api.RootCA, api.RootRotation, or api.CAConfig has a signing key (local signer) +func hasSigningKey(a interface{}) bool { + switch b := a.(type) { + case api.RootCA: + return len(b.CAKey) > 0 + case *api.RootRotation: + return b != nil && len(b.CAKey) > 0 + case api.CAConfig: + return len(b.SigningCACert) > 0 && len(b.SigningCAKey) > 0 + default: + panic("needsExternalCAs should be called something of type api.RootCA, *api.RootRotation, or api.CAConfig") + } +} + +// Creates a cross-signed intermediate and new api.RootRotation object. +// This function assumes that the root cert and key and the external CAs have already been validated. +func newRootRotationObject(ctx context.Context, securityConfig *ca.SecurityConfig, cluster *api.Cluster, newRootCA ca.RootCA, version uint64) (*api.RootCA, error) { + var ( + rootCert, rootKey, crossSignedCert []byte + newRootHasSigner bool + err error + ) + + rootCert = newRootCA.Certs + if s, err := newRootCA.Signer(); err == nil { + rootCert, rootKey = s.Cert, s.Key + newRootHasSigner = true + } + + // we have to sign with the original signer, not whatever is in the SecurityConfig's RootCA (which may have an intermediate signer, if + // a root rotation is already in progress) + switch { + case hasSigningKey(cluster.RootCA): + var oldRootCA ca.RootCA + oldRootCA, err = ca.NewRootCA(cluster.RootCA.CACert, cluster.RootCA.CACert, cluster.RootCA.CAKey, ca.DefaultNodeCertExpiration, nil) + if err == nil { + crossSignedCert, err = oldRootCA.CrossSignCACertificate(rootCert) + } + case !newRootHasSigner: // the original CA and the new CA both require external CAs + return nil, grpc.Errorf(codes.InvalidArgument, "rotating from one external CA to a different external CA is not supported") + default: + // We need the same credentials but to connect to the original URLs (in case we are in the middle of a root rotation already) + externalCA := securityConfig.ExternalCA().Copy() + var urls []string + for _, c := range cluster.Spec.CAConfig.ExternalCAs { + if c.Protocol == api.ExternalCA_CAProtocolCFSSL && bytes.Equal(c.CACert, cluster.RootCA.CACert) { + urls = append(urls, c.URL) + } + } + if len(urls) == 0 { + return nil, grpc.Errorf(codes.InvalidArgument, + "must provide an external CA for the current external root CA to generate a cross-signed certificate") + } + externalCA.UpdateURLs(urls...) + crossSignedCert, err = externalCA.CrossSignRootCA(ctx, newRootCA) + } + + if err != nil { + log.G(ctx).WithError(err).Error("unable to generate a cross-signed certificate for root rotation") + return nil, grpc.Errorf(codes.Internal, "unable to generate a cross-signed certificate for root rotation") + } + + copied := cluster.RootCA.Copy() + copied.RootRotation = &api.RootRotation{ + CACert: rootCert, + CAKey: rootKey, + CrossSignedCACert: crossSignedCert, + } + copied.LastForcedRotation = version + return copied, nil +} + +// Checks that a CA URL is connectable using the credentials we have and that its server certificate is signed by the +// root CA that we expect. This uses a TCP dialer rather than an HTTP client; because we have custom TLS configuration, +// if we wanted to use an HTTP client we'd have to create a new transport for every connection. The docs specify that +// Transports cache connections for future re-use, which could cause many open connections. +func validateExternalCAURL(dialer *net.Dialer, tlsOpts *tls.Config, caURL string) error { + parsed, err := url.Parse(caURL) + if err != nil { + return err + } + if parsed.Scheme != "https" { + return errors.New("invalid HTTP scheme") + } + host, port, err := net.SplitHostPort(parsed.Host) + if err != nil { + // It either has no port or is otherwise invalid (e.g. too many colons). If it's otherwise invalid the dialer + // will error later, so just assume it's no port and set the port to the default HTTPS port. + host = parsed.Host + port = "443" + } + + conn, err := tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(host, port), tlsOpts) + if conn != nil { + conn.Close() + } + return err +} + +// Iterates over all the external CAs, and validates that there is at least 1 reachable, valid external CA for the +// given CA certificate. Returns true if there is, false otherwise. +func hasAtLeastOneExternalCA(ctx context.Context, externalCAs []*api.ExternalCA, securityConfig *ca.SecurityConfig, wantedCert []byte) bool { + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(wantedCert) + dialer := net.Dialer{Timeout: 5 * time.Second} + opts := tls.Config{ + RootCAs: pool, + Certificates: securityConfig.ClientTLSCreds.Config().Certificates, + } + for i, ca := range externalCAs { + if ca.Protocol == api.ExternalCA_CAProtocolCFSSL && bytes.Equal(wantedCert, ca.CACert) { + err := validateExternalCAURL(&dialer, &opts, ca.URL) + if err == nil { + return true + } + log.G(ctx).WithError(err).Warnf("external CA # %d is unreachable or invalid", i+1) + } + } + return false +} + +// All new external CA definitions must include the CA cert associated with the external CA. +// If the current root CA requires an external CA, then at least one, reachable valid external CA must be provided that +// corresponds with the current RootCA's certificate. +// +// Similarly for the desired CA certificate, if one is specified. Similarly for the current outstanding root CA rotation, +// if one is specified and will not be replaced with the desired CA. +func validateHasRequiredExternalCAs(ctx context.Context, securityConfig *ca.SecurityConfig, cluster *api.Cluster) error { + config := cluster.Spec.CAConfig + for _, ca := range config.ExternalCAs { + if len(ca.CACert) == 0 { + return grpc.Errorf(codes.InvalidArgument, "must specify CA certificate for each external CA") + } + } + + if !hasSigningKey(cluster.RootCA) && !hasAtLeastOneExternalCA(ctx, config.ExternalCAs, securityConfig, cluster.RootCA.CACert) { + return grpc.Errorf(codes.InvalidArgument, "there must be at least one valid, reachable external CA corresponding to the current CA certificate") + } + + if len(config.SigningCACert) > 0 { // a signing cert is specified + if !hasSigningKey(config) && !hasAtLeastOneExternalCA(ctx, config.ExternalCAs, securityConfig, config.SigningCACert) { + return grpc.Errorf(codes.InvalidArgument, "there must be at least one valid, reachable external CA corresponding to the desired CA certificate") + } + } else if config.ForceRotate == cluster.RootCA.LastForcedRotation && cluster.RootCA.RootRotation != nil { + // no cert is specified but force rotation hasn't changed (so we are happy with the current configuration) and there's an outstanding root rotation + if !hasSigningKey(cluster.RootCA.RootRotation) && !hasAtLeastOneExternalCA(ctx, config.ExternalCAs, securityConfig, cluster.RootCA.RootRotation.CACert) { + return grpc.Errorf(codes.InvalidArgument, "there must be at least one valid, reachable external CA corresponding to the next CA certificate") + } + } + + return nil +} + +// validateAndUpdateCA validates a cluster's desired CA configuration spec, and returns a RootCA value on success representing +// current RootCA as it should be. Validation logic and return values are as follows: +// 1. Validates that the contents are complete - e.g. a signing key is not provided without a signing cert, and that external +// CAs are not removed if they are needed. Otherwise, returns an error. +// 2. If no desired signing cert or key are provided, then either: +// - we are happy with the current CA configuration (force rotation value has not changed), and we return the current RootCA +// object as is +// - we want to generate a new internal CA cert and key (force rotation value has changed), and we return the updated RootCA +// object +// 3. Signing cert and key have been provided: validate that these match (the cert and key match). Otherwise, return an error. +// 4. Return the updated RootCA object according to the following criteria: +// - If the desired cert is the same as the current CA cert then abort any outstanding rotations. The current signing key +// is replaced with the desired signing key (this could lets us switch between external->internal or internal->external +// without an actual CA rotation, which is not needed because any leaf cert issued with one CA cert can be validated using +// the second CA certificate). +// - If the desired cert is the same as the current to-be-rotated-to CA cert then a new root rotation is not needed. The +// current to-be-rotated-to signing key is replaced with the desired signing key (this could lets us switch between +// external->internal or internal->external without an actual CA rotation, which is not needed because any leaf cert +// issued with one CA cert can be validated using the second CA certificate). +// - Otherwise, start a new root rotation using the desired signing cert and desired signing key as the root rotation +// signing cert and key. If a root rotation is already in progress, just replace it and start over. +func validateCAConfig(ctx context.Context, securityConfig *ca.SecurityConfig, cluster *api.Cluster) (*api.RootCA, error) { + newConfig := cluster.Spec.CAConfig + + if len(newConfig.SigningCAKey) > 0 && len(newConfig.SigningCACert) == 0 { + return nil, grpc.Errorf(codes.InvalidArgument, "if a signing CA key is provided, the signing CA cert must also be provided") + } + + if err := validateHasRequiredExternalCAs(ctx, securityConfig, cluster); err != nil { + return nil, err + } + + // if the desired CA cert and key are not set, then we are happy with the current root CA configuration, unless + // the ForceRotate version has changed + if len(newConfig.SigningCACert) == 0 { + if cluster.RootCA.LastForcedRotation != newConfig.ForceRotate { + newRootCA, err := ca.CreateRootCA(ca.DefaultRootCN) + if err != nil { + return nil, grpc.Errorf(codes.Internal, err.Error()) + } + return newRootRotationObject(ctx, securityConfig, cluster, newRootCA, newConfig.ForceRotate) + } + return &cluster.RootCA, nil // no change, return as is + } + + // A desired cert and maybe key were provided - we need to make sure the cert and key (if provided) match. + var signingCert []byte + if hasSigningKey(newConfig) { + signingCert = newConfig.SigningCACert + } + newRootCA, err := ca.NewRootCA(newConfig.SigningCACert, signingCert, newConfig.SigningCAKey, ca.DefaultNodeCertExpiration, nil) + if err != nil { + return nil, grpc.Errorf(codes.InvalidArgument, err.Error()) + } + + if len(newRootCA.Pool.Subjects()) != 1 { + return nil, grpc.Errorf(codes.InvalidArgument, "the desired CA certificate cannot contain multiple certificates") + } + + parsedCert, err := helpers.ParseCertificatePEM(newConfig.SigningCACert) + if err != nil { + return nil, grpc.Errorf(codes.InvalidArgument, "could not parse the desired CA certificate") + } + + // The new certificate's expiry must be at least one year away + if parsedCert.NotAfter.Before(time.Now().Add(minRootExpiration)) { + return nil, grpc.Errorf(codes.InvalidArgument, "CA certificate expires too soon") + } + + // check if we can abort any existing root rotations + if bytes.Equal(cluster.RootCA.CACert, cluster.Spec.CAConfig.SigningCACert) { + copied := cluster.RootCA.Copy() + copied.CAKey = newConfig.SigningCAKey + copied.RootRotation = nil + copied.LastForcedRotation = newConfig.ForceRotate + return copied, nil + } + + // check if this is the same desired cert as an existing root rotation + if r := cluster.RootCA.RootRotation; r != nil && bytes.Equal(r.CACert, cluster.Spec.CAConfig.SigningCACert) { + copied := cluster.RootCA.Copy() + copied.RootRotation.CAKey = newConfig.SigningCAKey + copied.LastForcedRotation = newConfig.ForceRotate + return copied, nil + } + + // ok, everything's different; we have to begin a new root rotation which means generating a new cross-signed cert + return newRootRotationObject(ctx, securityConfig, cluster, newRootCA, newConfig.ForceRotate) +} diff --git a/manager/controlapi/ca_rotation_test.go b/manager/controlapi/ca_rotation_test.go new file mode 100644 index 0000000000..a4f2039739 --- /dev/null +++ b/manager/controlapi/ca_rotation_test.go @@ -0,0 +1,671 @@ +package controlapi + +import ( + "context" + "crypto/x509" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/cloudflare/cfssl/helpers" + "github.com/cloudflare/cfssl/initca" + "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/ca" + "github.com/docker/swarmkit/ca/testutils" + "github.com/docker/swarmkit/manager/state/store" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +type rootCARotationTestCase struct { + rootCA api.RootCA + caConfig api.CAConfig + + // what to expect if the validate and update succeeds - we can't always check that everything matches, for instance if + // random values for join tokens or cross signed certs, or generated root rotation cert/key, + // are expected + expectRootCA api.RootCA + expectJoinTokenChange bool + expectGeneratedRootRotation bool + expectGeneratedCross bool + description string // in case an expectation fails + + // what error string to expect if the validate fails + expectErrorString string +} + +var initialLocalRootCA = api.RootCA{ + CACert: testutils.ECDSA256SHA256Cert, + CAKey: testutils.ECDSA256Key, + CACertHash: "DEADBEEF", + JoinTokens: api.JoinTokens{ + Worker: "SWMTKN-1-worker", + Manager: "SWMTKN-1-manager", + }, +} +var rotationCert, rotationKey = testutils.ECDSACertChain[2], testutils.ECDSACertChainKeys[2] + +func getSecurityConfig(t *testing.T, localRootCA *ca.RootCA, cluster *api.Cluster) *ca.SecurityConfig { + tempdir, err := ioutil.TempDir("", "test-validate-CA") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + paths := ca.NewConfigPaths(tempdir) + secConfig, err := localRootCA.CreateSecurityConfig(context.Background(), ca.NewKeyReadWriter(paths.Node, nil, nil), ca.CertificateRequestConfig{}) + require.NoError(t, err) + + require.NoError(t, ca.NewServer(store.NewMemoryStore(nil), secConfig).UpdateRootCA(context.Background(), cluster)) + return secConfig +} + +func TestValidateCAConfigInvalidValues(t *testing.T) { + t.Parallel() + localRootCA, err := ca.NewRootCA(initialLocalRootCA.CACert, initialLocalRootCA.CACert, initialLocalRootCA.CAKey, + ca.DefaultNodeCertExpiration, nil) + require.NoError(t, err) + + initialExternalRootCA := initialLocalRootCA + initialExternalRootCA.CAKey = nil + + crossSigned, err := localRootCA.CrossSignCACertificate(rotationCert) + require.NoError(t, err) + + initExternalRootCAWithRotation := initialExternalRootCA + initExternalRootCAWithRotation.RootRotation = &api.RootRotation{ + CACert: rotationCert, + CAKey: rotationKey, + CrossSignedCACert: crossSigned, + } + + initWithExternalRootRotation := initialLocalRootCA + initWithExternalRootRotation.RootRotation = &api.RootRotation{ + CACert: rotationCert, + CrossSignedCACert: crossSigned, + } + + // set up 2 external CAs that can be contacted for signing + tempdir, err := ioutil.TempDir("", "test-validate-CA") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + initExtServer, err := testutils.NewExternalSigningServer(localRootCA, tempdir) + require.NoError(t, err) + defer initExtServer.Stop() + + // we need to accept client certs from the original cert + rotationRootCA, err := ca.NewRootCA(append(initialLocalRootCA.CACert, rotationCert...), rotationCert, rotationKey, + ca.DefaultNodeCertExpiration, nil) + require.NoError(t, err) + rotateExtServer, err := testutils.NewExternalSigningServer(rotationRootCA, tempdir) + require.NoError(t, err) + defer rotateExtServer.Stop() + + for _, invalid := range []rootCARotationTestCase{ + { + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCAKey: initialLocalRootCA.CAKey, + }, + expectErrorString: "the signing CA cert must also be provided", + }, + { + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + }, + }, + }, + expectErrorString: "must specify CA certificate for each external CA", + }, + { + rootCA: initExternalRootCAWithRotation, // even if a root rotation is already in progress, the current CA external URL must be present + caConfig: api.CAConfig{ + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: initialLocalRootCA.CACert, + Protocol: 3, // wrong protocol + }, + { + URL: initExtServer.URL, + CACert: rotationCert, // wrong cert + }, + }, + }, + expectErrorString: "there must be at least one valid, reachable external CA corresponding to the current CA certificate", + }, + { + rootCA: initialExternalRootCA, + caConfig: api.CAConfig{ + SigningCACert: rotationCert, // even if there's a desired cert, the current CA external URL must be present + ExternalCAs: []*api.ExternalCA{ // right certs, but invalid URLs in several ways + { + URL: rotateExtServer.URL, + CACert: initialExternalRootCA.CACert, + }, + { + URL: "invalidurl", + CACert: initialExternalRootCA.CACert, + }, + { + URL: "https://too:many:colons:1:2:3", + CACert: initialExternalRootCA.CACert, + }, + }, + }, + expectErrorString: "there must be at least one valid, reachable external CA corresponding to the current CA certificate", + }, + { + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: rotationCert, + ExternalCAs: []*api.ExternalCA{ + { + URL: rotateExtServer.URL, + CACert: rotationCert, + Protocol: 3, // wrong protocol + }, + { + URL: rotateExtServer.URL, + CACert: initialLocalRootCA.CACert, // wrong cert + }, + }, + }, + expectErrorString: "there must be at least one valid, reachable external CA corresponding to the desired CA certificate", + }, + { + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: rotationCert, + ExternalCAs: []*api.ExternalCA{ // right certs, but invalid URLs in several ways + { + URL: initExtServer.URL, + CACert: rotationCert, + }, + { + URL: "invalidurl", + CACert: rotationCert, + }, + { + URL: "https://too:many:colons:1:2:3", + CACert: initialExternalRootCA.CACert, + }, + }, + }, + expectErrorString: "there must be at least one valid, reachable external CA corresponding to the desired CA certificate", + }, + { + rootCA: initWithExternalRootRotation, + caConfig: api.CAConfig{ // no forceRotate change, no explicit signing cert change + ExternalCAs: []*api.ExternalCA{ + { + URL: rotateExtServer.URL, + CACert: rotationCert, + Protocol: 3, // wrong protocol + }, + { + URL: rotateExtServer.URL, + CACert: initialLocalRootCA.CACert, // wrong cert + }, + }, + }, + expectErrorString: "there must be at least one valid, reachable external CA corresponding to the next CA certificate", + }, + { + rootCA: initWithExternalRootRotation, + caConfig: api.CAConfig{ // no forceRotate change, no explicit signing cert change + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: rotationCert, + // right CA cert, but the server cert is not signed by this CA cert + }, + { + URL: "invalidurl", + CACert: rotationCert, + // right CA cert, but invalid URL + }, + }, + }, + expectErrorString: "there must be at least one valid, reachable external CA corresponding to the next CA certificate", + }, + { + rootCA: initialExternalRootCA, + caConfig: api.CAConfig{ + SigningCACert: rotationCert, + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: initialLocalRootCA.CACert, // current cert + }, + { + URL: rotateExtServer.URL, + CACert: rotationCert, //new cert + }, + }, + }, + expectErrorString: "rotating from one external CA to a different external CA is not supported", + }, + { + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: append(rotationCert, initialLocalRootCA.CACert...), + SigningCAKey: rotationKey, + }, + expectErrorString: "cannot contain multiple certificates", + }, + { + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: testutils.ReDateCert(t, rotationCert, rotationCert, rotationKey, + time.Now().Add(-1*time.Minute), time.Now().Add(364*helpers.OneDay)), + SigningCAKey: rotationKey, + }, + expectErrorString: "expires too soon", + }, + { + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: initialLocalRootCA.CACert, + SigningCAKey: testutils.ExpiredKey, // same cert but mismatching key + }, + expectErrorString: "certificate key mismatch", + }, + { + // this is just one class of failures caught by NewRootCA, not going to bother testing others, since they are + // extensively tested in NewRootCA + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: testutils.ExpiredCert, + SigningCAKey: testutils.ExpiredKey, + }, + expectErrorString: "expired", + }, + } { + cluster := &api.Cluster{ + RootCA: invalid.rootCA, + Spec: api.ClusterSpec{ + CAConfig: invalid.caConfig, + }, + } + secConfig := getSecurityConfig(t, &localRootCA, cluster) + _, err := validateCAConfig(context.Background(), secConfig, cluster) + require.Error(t, err, invalid.expectErrorString) + require.Equal(t, codes.InvalidArgument, grpc.Code(err), invalid.expectErrorString) + require.Contains(t, grpc.ErrorDesc(err), invalid.expectErrorString) + } +} + +func runValidTestCases(t *testing.T, testcases []*rootCARotationTestCase, localRootCA *ca.RootCA) { + for _, valid := range testcases { + cluster := &api.Cluster{ + RootCA: *valid.rootCA.Copy(), + Spec: api.ClusterSpec{ + CAConfig: valid.caConfig, + }, + } + secConfig := getSecurityConfig(t, localRootCA, cluster) + result, err := validateCAConfig(context.Background(), secConfig, cluster) + require.NoError(t, err, valid.description) + + // ensure that the cluster was not mutated + require.Equal(t, valid.rootCA, cluster.RootCA) + + // Because join tokens are random, we can't predict exactly what it is, so this needs to be manually checked + if valid.expectJoinTokenChange { + require.NotEmpty(t, result.JoinTokens, valid.rootCA.JoinTokens, valid.description) + } else { + require.Equal(t, result.JoinTokens, valid.rootCA.JoinTokens, valid.description) + } + result.JoinTokens = valid.expectRootCA.JoinTokens + + // If a cross-signed certificates is generated, we cant know what it is ahead of time. All we can do is check that it's + // correctly generated. + if valid.expectGeneratedCross || valid.expectGeneratedRootRotation { // both generate cross signed certs + require.NotNil(t, result.RootRotation, valid.description) + require.NotEmpty(t, result.RootRotation.CrossSignedCACert, valid.description) + + // make sure the cross-signed cert is signed by the current root CA (and not an intermediate, if a root rotation is in progress) + parsedCross, err := helpers.ParseCertificatePEM(result.RootRotation.CrossSignedCACert) // there should just be one + require.NoError(t, err) + _, err = parsedCross.Verify(x509.VerifyOptions{Roots: localRootCA.Pool}) + require.NoError(t, err, valid.description) + + // if we are expecting generated certs or root rotation, we can expect the expected root CA has a root rotation + result.RootRotation.CrossSignedCACert = valid.expectRootCA.RootRotation.CrossSignedCACert + } + + // If a root rotation cert is generated, we can't assert what the cert and key are. So if we expect it to be generated, + // just assert that the value has changed. + if valid.expectGeneratedRootRotation { + require.NotNil(t, result.RootRotation, valid.description) + require.NotEqual(t, valid.rootCA.RootRotation, result.RootRotation, valid.description) + result.RootRotation = valid.expectRootCA.RootRotation + } + + require.Equal(t, result, &valid.expectRootCA, valid.description) + } +} + +func TestValidateCAConfigValidValues(t *testing.T) { + t.Parallel() + localRootCA, err := ca.NewRootCA(testutils.ECDSA256SHA256Cert, testutils.ECDSA256SHA256Cert, testutils.ECDSA256Key, + ca.DefaultNodeCertExpiration, nil) + require.NoError(t, err) + + parsedCert, err := helpers.ParseCertificatePEM(testutils.ECDSA256SHA256Cert) + require.NoError(t, err) + parsedKey, err := helpers.ParsePrivateKeyPEM(testutils.ECDSA256Key) + require.NoError(t, err) + + initialExternalRootCA := initialLocalRootCA + initialExternalRootCA.CAKey = nil + + // set up 2 external CAs that can be contacted for signing + tempdir, err := ioutil.TempDir("", "test-validate-CA") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + initExtServer, err := testutils.NewExternalSigningServer(localRootCA, tempdir) + require.NoError(t, err) + defer initExtServer.Stop() + require.NoError(t, initExtServer.EnableCASigning()) + + // we need to accept client certs from the original cert + rotationRootCA, err := ca.NewRootCA(append(initialLocalRootCA.CACert, rotationCert...), rotationCert, rotationKey, + ca.DefaultNodeCertExpiration, nil) + require.NoError(t, err) + rotateExtServer, err := testutils.NewExternalSigningServer(rotationRootCA, tempdir) + require.NoError(t, err) + defer rotateExtServer.Stop() + require.NoError(t, rotateExtServer.EnableCASigning()) + + getExpectedRootCA := func(hasKey bool) api.RootCA { + result := initialLocalRootCA + result.LastForcedRotation = 5 + result.JoinTokens = api.JoinTokens{} + if !hasKey { + result.CAKey = nil + } + return result + } + getRootCAWithRotation := func(base api.RootCA, cert, key, cross []byte) api.RootCA { + init := base + init.RootRotation = &api.RootRotation{ + CACert: cert, + CAKey: key, + CrossSignedCACert: cross, + } + return init + } + + // These require no rotation, because the cert is exactly the same. + testcases := []*rootCARotationTestCase{ + { + description: "same desired cert and key as current Root CA results in no root rotation", + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: initialLocalRootCA.CACert, + SigningCAKey: initialLocalRootCA.CAKey, + ForceRotate: 5, + }, + expectRootCA: getExpectedRootCA(true), + }, + { + description: "same desired cert as current Root CA but external->internal results in no root rotation and no key -> key", + rootCA: initialExternalRootCA, + caConfig: api.CAConfig{ + SigningCACert: initialLocalRootCA.CACert, + SigningCAKey: initialLocalRootCA.CAKey, + ForceRotate: 5, + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: initialLocalRootCA.CACert, + }, + }, + }, + expectRootCA: getExpectedRootCA(true), + }, + { + description: "same desired cert as current Root CA but internal->external results in no root rotation and key -> no key", + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: initialLocalRootCA.CACert, + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: initialLocalRootCA.CACert, + }, + }, + ForceRotate: 5, + }, + expectRootCA: getExpectedRootCA(false), + }, + } + runValidTestCases(t, testcases, &localRootCA) + + // These will abort root rotation because the desired cert is the same as the current RootCA cert + crossSigned, err := localRootCA.CrossSignCACertificate(rotationCert) + require.NoError(t, err) + for _, testcase := range testcases { + testcase.rootCA = getRootCAWithRotation(testcase.rootCA, rotationCert, rotationKey, crossSigned) + } + testcases[0].description = "same desired cert and key as current RootCA results in aborting root rotation" + testcases[1].description = "same desired cert, even if external->internal, as current RootCA results in aborting root rotation and no key -> key" + testcases[2].description = "same desired cert, even if internal->external, as current RootCA results in aborting root rotation and key -> no key" + runValidTestCases(t, testcases, &localRootCA) + + // These will not change the root rotation because the desired cert is the same as the current to-be-rotated-to cert + expectedBaseRootCA := getExpectedRootCA(true) // the main root CA expected will always have a signing key + testcases = []*rootCARotationTestCase{ + { + description: "same desired cert and key as current root rotation results in no change in root rotation", + rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, rotationKey, crossSigned), + caConfig: api.CAConfig{ + SigningCACert: testutils.ECDSACertChain[2], + SigningCAKey: testutils.ECDSACertChainKeys[2], + ForceRotate: 5, + }, + expectRootCA: getRootCAWithRotation(expectedBaseRootCA, rotationCert, rotationKey, crossSigned), + }, + { + description: "same desired cert as current root rotation but external->internal results minor change in root rotation (no key -> key)", + rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, nil, crossSigned), + caConfig: api.CAConfig{ + SigningCACert: testutils.ECDSACertChain[2], + SigningCAKey: testutils.ECDSACertChainKeys[2], + ForceRotate: 5, + }, + expectRootCA: getRootCAWithRotation(expectedBaseRootCA, rotationCert, rotationKey, crossSigned), + }, + { + description: "same desired cert as current root rotation but internal->external results minor change in root rotation (key -> no key)", + rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, rotationKey, crossSigned), + caConfig: api.CAConfig{ + SigningCACert: testutils.ECDSACertChain[2], + ForceRotate: 5, + ExternalCAs: []*api.ExternalCA{ + { + URL: rotateExtServer.URL, + CACert: testutils.ECDSACertChain[2], + }, + }, + }, + expectRootCA: getRootCAWithRotation(expectedBaseRootCA, rotationCert, nil, crossSigned), + }, + } + runValidTestCases(t, testcases, &localRootCA) + + // These all require a new root rotation because the desired cert is different, even if it has the same key and/or subject as the current + // cert or the current-to-be-rotated cert. + renewedInitialCert, err := initca.RenewFromSigner(parsedCert, parsedKey) + require.NoError(t, err) + parsedRotationCert, err := helpers.ParseCertificatePEM(rotationCert) + require.NoError(t, err) + parsedRotationKey, err := helpers.ParsePrivateKeyPEM(rotationKey) + require.NoError(t, err) + renewedRotationCert, err := initca.RenewFromSigner(parsedRotationCert, parsedRotationKey) + require.NoError(t, err) + differentInitialCert, err := testutils.CreateCertFromSigner("otherRootCN", parsedKey) + require.NoError(t, err) + differentRootCA, err := ca.NewRootCA(append(initialLocalRootCA.CACert, differentInitialCert...), differentInitialCert, + initialLocalRootCA.CAKey, ca.DefaultNodeCertExpiration, nil) + require.NoError(t, err) + differentExtServer, err := testutils.NewExternalSigningServer(differentRootCA, tempdir) + require.NoError(t, err) + defer differentExtServer.Stop() + require.NoError(t, differentExtServer.EnableCASigning()) + testcases = []*rootCARotationTestCase{ + { + description: "desired cert being a renewed current cert and key results in a root rotation because the cert has changed", + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: renewedInitialCert, + SigningCAKey: initialLocalRootCA.CAKey, + ForceRotate: 5, + }, + expectRootCA: getRootCAWithRotation(expectedBaseRootCA, renewedInitialCert, initialLocalRootCA.CAKey, nil), + expectGeneratedCross: true, + }, + { + description: "desired cert being a renewed current cert, external->internal results in a root rotation because the cert has changed", + rootCA: initialExternalRootCA, + caConfig: api.CAConfig{ + SigningCACert: renewedInitialCert, + SigningCAKey: initialLocalRootCA.CAKey, + ForceRotate: 5, + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: initialLocalRootCA.CACert, + }, + }, + }, + expectRootCA: getRootCAWithRotation(getExpectedRootCA(false), renewedInitialCert, initialLocalRootCA.CAKey, nil), + expectGeneratedCross: true, + }, + { + description: "desired cert eing a renewed current cert, internal->external results in a root rotation because the cert has changed", + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ + SigningCACert: renewedInitialCert, + ForceRotate: 5, + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: renewedInitialCert, + }, + }, + }, + expectRootCA: getRootCAWithRotation(expectedBaseRootCA, renewedInitialCert, nil, nil), + expectGeneratedCross: true, + }, + { + description: "desired cert being a renewed rotation RootCA cert + rotation key results in replaced root rotation because the cert has changed", + rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, rotationKey, crossSigned), + caConfig: api.CAConfig{ + SigningCACert: renewedRotationCert, + SigningCAKey: rotationKey, + ForceRotate: 5, + }, + expectRootCA: getRootCAWithRotation(expectedBaseRootCA, renewedRotationCert, rotationKey, nil), + expectGeneratedCross: true, + }, + { + description: "desired cert being a different rotation rootCA cert results in replaced root rotation (only new external CA required, not old rotation external CA)", + rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, nil, crossSigned), + caConfig: api.CAConfig{ + SigningCACert: differentInitialCert, + ForceRotate: 5, + ExternalCAs: []*api.ExternalCA{ + { + // we need a different external server, because otherwise the external server's cert will fail to validate + // (not signed by the right cert - note that there's a bug in go 1.7 where this is not needed, because the + // subject names of cert names aren't checked, but go 1.8 fixes this.) + URL: differentExtServer.URL, + CACert: differentInitialCert, + }, + }, + }, + expectRootCA: getRootCAWithRotation(expectedBaseRootCA, differentInitialCert, nil, nil), + expectGeneratedCross: true, + }, + } + runValidTestCases(t, testcases, &localRootCA) + + // These require rotation because the cert and key are generated and hence completely different. + testcases = []*rootCARotationTestCase{ + { + description: "generating cert and key results in root rotation", + rootCA: initialLocalRootCA, + caConfig: api.CAConfig{ForceRotate: 5}, + expectRootCA: getRootCAWithRotation(getExpectedRootCA(true), nil, nil, nil), + expectGeneratedRootRotation: true, + }, + { + description: "generating cert for external->internal results in root rotation", + rootCA: initialExternalRootCA, + caConfig: api.CAConfig{ + ForceRotate: 5, + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: initialExternalRootCA.CACert, + }, + }, + }, + expectRootCA: getRootCAWithRotation(getExpectedRootCA(false), nil, nil, nil), + expectGeneratedRootRotation: true, + }, + { + description: "generating cert and key results in replacing root rotation", + rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, rotationKey, crossSigned), + caConfig: api.CAConfig{ForceRotate: 5}, + expectRootCA: getRootCAWithRotation(getExpectedRootCA(true), nil, nil, nil), + expectGeneratedRootRotation: true, + }, + { + description: "generating cert and key results in replacing root rotation; external CAs required by old root rotation are no longer necessary", + rootCA: getRootCAWithRotation(initialLocalRootCA, rotationCert, nil, crossSigned), + caConfig: api.CAConfig{ForceRotate: 5}, + expectRootCA: getRootCAWithRotation(getExpectedRootCA(true), nil, nil, nil), + expectGeneratedRootRotation: true, + }, + } + runValidTestCases(t, testcases, &localRootCA) + + // These require no change at all because the force rotate value hasn't changed, and there is no desired cert specified + testcases = []*rootCARotationTestCase{ + { + description: "no desired certificate specified, no force rotation: no change to internal signer root (which has no outstanding rotation)", + rootCA: initialLocalRootCA, + expectRootCA: initialLocalRootCA, + }, + { + description: "no desired certificate specified, no force rotation: no change to external CA root (which has no outstanding rotation)", + rootCA: initialExternalRootCA, + caConfig: api.CAConfig{ + ExternalCAs: []*api.ExternalCA{ + { + URL: initExtServer.URL, + CACert: initialExternalRootCA.CACert, + }, + }, + }, + expectRootCA: initialExternalRootCA, + }, + } + runValidTestCases(t, testcases, &localRootCA) + + for _, testcase := range testcases { + testcase.rootCA = getRootCAWithRotation(testcase.rootCA, rotationCert, rotationKey, crossSigned) + testcase.expectRootCA = testcase.rootCA + } + testcases[0].description = "no desired certificate specified, no force rotation: no change to internal signer root or to outstanding rotation" + testcases[1].description = "no desired certificate specified, no force rotation: no change to external CA root or to outstanding rotation" + runValidTestCases(t, testcases, &localRootCA) +} From 1b1c0300eacd415f2c8f160708f9d3747a8313dc Mon Sep 17 00:00:00 2001 From: cyli Date: Tue, 28 Mar 2017 17:36:30 -0700 Subject: [PATCH 3/3] Use the CAConfig spec validation function in the control api's UpdateCluster function. Also make sure that when listing and getting clusters, the CAConfig's signing key and the root rotation object's signing key are redacted, if present. Signed-off-by: cyli --- manager/controlapi/cluster.go | 30 ++++++++++++------- manager/controlapi/cluster_test.go | 48 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/manager/controlapi/cluster.go b/manager/controlapi/cluster.go index 5ff446836f..616d4d2f54 100644 --- a/manager/controlapi/cluster.go +++ b/manager/controlapi/cluster.go @@ -103,7 +103,6 @@ func (s *Server) UpdateCluster(ctx context.Context, request *api.UpdateClusterRe cluster = store.GetCluster(tx, request.ClusterID) if cluster == nil { return grpc.Errorf(codes.NotFound, "cluster %s not found", request.ClusterID) - } // This ensures that we always have the latest security config, so our ca.SecurityConfig.RootCA and // ca.SecurityConfig.externalCA objects are up-to-date with the current api.Cluster.RootCA and @@ -129,6 +128,12 @@ func (s *Server) UpdateCluster(ctx context.Context, request *api.UpdateClusterRe cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(rootCA) } + updatedRootCA, err := validateCAConfig(ctx, s.securityConfig, cluster) + if err != nil { + return err + } + cluster.RootCA = *updatedRootCA + var unlockKeys []*api.EncryptionKey var managerKey *api.EncryptionKey for _, eKey := range cluster.UnlockKeys { @@ -239,19 +244,22 @@ func redactClusters(clusters []*api.Cluster) []*api.Cluster { // Only add public fields to the new clusters for _, cluster := range clusters { // Copy all the mandatory fields - // Do not copy secret key + // Do not copy secret keys + redactedSpec := cluster.Spec.Copy() + redactedSpec.CAConfig.SigningCAKey = nil + + redactedRootCA := cluster.RootCA.Copy() + redactedRootCA.CAKey = nil + if r := redactedRootCA.RootRotation; r != nil { + r.CAKey = nil + } newCluster := &api.Cluster{ - ID: cluster.ID, - Meta: cluster.Meta, - Spec: cluster.Spec, - RootCA: api.RootCA{ - CACert: cluster.RootCA.CACert, - CACertHash: cluster.RootCA.CACertHash, - JoinTokens: cluster.RootCA.JoinTokens, - }, + ID: cluster.ID, + Meta: cluster.Meta, + Spec: *redactedSpec, + RootCA: *redactedRootCA, BlacklistedCertificates: cluster.BlacklistedCertificates, } - redactedClusters = append(redactedClusters, newCluster) } diff --git a/manager/controlapi/cluster_test.go b/manager/controlapi/cluster_test.go index f4e0f14678..372165dfe6 100644 --- a/manager/controlapi/cluster_test.go +++ b/manager/controlapi/cluster_test.go @@ -7,6 +7,7 @@ import ( "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" + "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/manager/state/store" "github.com/docker/swarmkit/protobuf/ptypes" gogotypes "github.com/gogo/protobuf/types" @@ -438,6 +439,53 @@ func TestUpdateClusterRotateUnlockKey(t *testing.T) { require.Nil(t, getManagerKey()) } +// root rotation tests have already been covered by ca_rotation_test.go - this test only makes sure that the function tested in those +// tests is actually called by `UpdateCluster`, and that the results of GetCluster and ListCluster have the rotation root CA key +// redacted +func TestUpdateClusterRootRotation(t *testing.T) { + ts := newTestServer(t) + defer ts.Stop() + + cluster := createCluster(t, ts, "id", "name", api.AcceptancePolicy{}, ts.Server.securityConfig.RootCA()) + response, err := ts.Client.GetCluster(context.Background(), &api.GetClusterRequest{ClusterID: cluster.ID}) + require.NoError(t, err) + require.NotNil(t, response.Cluster) + cluster = response.Cluster + + updatedSpec := cluster.Spec.Copy() + updatedSpec.CAConfig.SigningCACert = testutils.ECDSA256SHA256Cert + updatedSpec.CAConfig.SigningCAKey = testutils.ECDSA256Key + updatedSpec.CAConfig.ForceRotate = 5 + + _, err = ts.Client.UpdateCluster(context.Background(), &api.UpdateClusterRequest{ + ClusterID: cluster.ID, + Spec: updatedSpec, + ClusterVersion: &cluster.Meta.Version, + }) + require.NoError(t, err) + + var gottenClusters []*api.Cluster + + response, err = ts.Client.GetCluster(context.Background(), &api.GetClusterRequest{ClusterID: cluster.ID}) + require.NoError(t, err) + require.NotNil(t, response.Cluster) + gottenClusters = append(gottenClusters, response.Cluster) + + listResponse, err := ts.Client.ListClusters(context.Background(), &api.ListClustersRequest{}) + require.NoError(t, err) + require.Len(t, listResponse.Clusters, 1) + gottenClusters = append(gottenClusters, listResponse.Clusters[0]) + + for _, c := range gottenClusters { + require.NotNil(t, c.RootCA.RootRotation) + + // check signing key redaction + require.Len(t, c.RootCA.CAKey, 0) + require.Len(t, c.RootCA.RootRotation.CAKey, 0) + require.Len(t, c.Spec.CAConfig.SigningCAKey, 0) + } +} + func TestListClusters(t *testing.T) { ts := newTestServer(t) defer ts.Stop()