diff --git a/agent/agent.go b/agent/agent.go index d28be94385..4a11c69e2f 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -577,6 +577,7 @@ func (a *Agent) nodeDescriptionWithHostname(ctx context.Context, tlsInfo *api.No desc.Hostname = a.config.Hostname } desc.TLSInfo = tlsInfo + desc.FIPS = a.config.FIPS } return desc, err } diff --git a/agent/agent_test.go b/agent/agent_test.go index 9dc1a0c4cf..005c65eb8b 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -232,17 +232,20 @@ func TestHandleSessionMessageNodeChanges(t *testing.T) { require.Empty(t, closedSessions) } -// when the node description changes, the session is restarted and propagated up to the dispatcher +// when the node description changes, the session is restarted and propagated up to the dispatcher. +// the node description includes the FIPSness of the agent. func TestSessionRestartedOnNodeDescriptionChange(t *testing.T) { tlsCh := make(chan events.Event, 1) defer close(tlsCh) tester := agentTestEnv(t, nil, tlsCh) + tester.agent.config.FIPS = true // start out with the agent in FIPS-enabled mode defer tester.cleanup() defer tester.StartAgent(t)() currSession, closedSessions := tester.dispatcher.GetSessions() require.NotNil(t, currSession) require.NotNil(t, currSession.Description) + require.True(t, currSession.Description.FIPS) require.Empty(t, closedSessions) tester.executor.UpdateNodeDescription(&api.NodeDescription{ @@ -262,6 +265,7 @@ func TestSessionRestartedOnNodeDescriptionChange(t *testing.T) { require.NotEqual(t, currSession, gotSession) require.NotNil(t, gotSession.Description) require.Equal(t, "testAgent", gotSession.Description.Hostname) + require.True(t, gotSession.Description.FIPS) currSession = gotSession // If nothing changes, the session is not re-established @@ -291,6 +295,7 @@ func TestSessionRestartedOnNodeDescriptionChange(t *testing.T) { require.NotNil(t, gotSession.Description) require.Equal(t, "testAgent", gotSession.Description.Hostname) require.Equal(t, newTLSInfo, gotSession.Description.TLSInfo) + require.True(t, gotSession.Description.FIPS) } // If the dispatcher returns an error, if it times out, or if it's unreachable, no matter diff --git a/agent/config.go b/agent/config.go index 98d5bf5728..b4293e9e03 100644 --- a/agent/config.go +++ b/agent/config.go @@ -47,6 +47,9 @@ type Config struct { // SessionTracker, if provided, will have its SessionClosed and SessionError methods called // when sessions close and error. SessionTracker SessionTracker + + // FIPS returns whether the node is FIPS-enabled + FIPS bool } func (c *Config) validate() error { diff --git a/api/api.pb.txt b/api/api.pb.txt index 6d5dce80a2..d81acb202b 100755 --- a/api/api.pb.txt +++ b/api/api.pb.txt @@ -2171,6 +2171,16 @@ file { } json_name: "tlsInfo" } + field { + name: "fips" + number: 6 + label: LABEL_OPTIONAL + type: TYPE_BOOL + options { + 65004: "FIPS" + } + json_name: "fips" + } } message_type { name: "NodeTLSInfo" @@ -3928,6 +3938,13 @@ file { 66001: "NACLSecretboxSalsa20Poly1305" } } + value { + name: "FERNET_AES_128_CBC" + number: 2 + options { + 66001: "FernetAES128CBC" + } + } } } message_type { @@ -6150,6 +6167,16 @@ file { type_name: ".docker.swarmkit.v1.EncryptionKey" json_name: "unlockKeys" } + field { + name: "fips" + number: 10 + label: LABEL_OPTIONAL + type: TYPE_BOOL + options { + 65004: "FIPS" + } + json_name: "fips" + } nested_type { name: "BlacklistedCertificatesEntry" field { diff --git a/api/objects.pb.go b/api/objects.pb.go index 01abbe5076..a7d8d49b8c 100644 --- a/api/objects.pb.go +++ b/api/objects.pb.go @@ -270,6 +270,11 @@ type Cluster struct { // If the key is empty, the node will be unlocked (will not require a key // to start up from a shut down state). UnlockKeys []*EncryptionKey `protobuf:"bytes,9,rep,name=unlock_keys,json=unlockKeys" json:"unlock_keys,omitempty"` + // FIPS specifies whether this cluster should be in FIPS mode. This changes + // the format of the join tokens, and nodes that are not FIPS-enabled should + // reject joining the cluster. Nodes that report themselves to be non-FIPS + // should be rejected from the cluster. + FIPS bool `protobuf:"varint,10,opt,name=fips,proto3" json:"fips,omitempty"` } func (m *Cluster) Reset() { *m = Cluster{} } @@ -1426,6 +1431,16 @@ func (m *Cluster) MarshalTo(dAtA []byte) (int, error) { i += n } } + if m.FIPS { + dAtA[i] = 0x50 + i++ + if m.FIPS { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -1925,6 +1940,9 @@ func (m *Cluster) Size() (n int) { n += 1 + l + sovObjects(uint64(l)) } } + if m.FIPS { + n += 2 + } return n } @@ -4543,6 +4561,7 @@ func (this *Cluster) String() string { `EncryptionKeyLamportClock:` + fmt.Sprintf("%v", this.EncryptionKeyLamportClock) + `,`, `BlacklistedCertificates:` + mapStringForBlacklistedCertificates + `,`, `UnlockKeys:` + strings.Replace(fmt.Sprintf("%v", this.UnlockKeys), "EncryptionKey", "EncryptionKey", 1) + `,`, + `FIPS:` + fmt.Sprintf("%v", this.FIPS) + `,`, `}`, }, "") return s @@ -6956,6 +6975,26 @@ func (m *Cluster) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FIPS", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowObjects + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FIPS = bool(v != 0) default: iNdEx = preIndex skippy, err := skipObjects(dAtA[iNdEx:]) @@ -7752,101 +7791,102 @@ var ( func init() { proto.RegisterFile("github.com/docker/swarmkit/api/objects.proto", fileDescriptorObjects) } var fileDescriptorObjects = []byte{ - // 1527 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcf, 0x6f, 0x1b, 0x4f, - 0x15, 0xef, 0xda, 0x1b, 0xff, 0x78, 0x4e, 0x4c, 0x98, 0x86, 0xb0, 0x35, 0xc1, 0x0e, 0xae, 0x40, - 0x55, 0x55, 0x39, 0x25, 0x14, 0x48, 0x03, 0xa5, 0xb5, 0x93, 0xa8, 0xb5, 0x4a, 0x69, 0x34, 0x2d, - 0x2d, 0xb7, 0x65, 0xb2, 0x3b, 0x75, 0x17, 0xaf, 0x77, 0x56, 0x3b, 0x63, 0x17, 0xdf, 0x7a, 0x0e, - 0x7f, 0x40, 0x6e, 0x1c, 0xfa, 0x37, 0x70, 0xe1, 0xc2, 0x81, 0x53, 0x8f, 0x9c, 0x10, 0xa7, 0x88, - 0xfa, 0xbf, 0x40, 0xe2, 0x80, 0x66, 0x76, 0xd6, 0xde, 0xc4, 0x9b, 0x5f, 0xa8, 0x8a, 0xbe, 0xa7, - 0xcc, 0xec, 0x7c, 0x3e, 0xef, 0xd7, 0xbc, 0xf7, 0xe6, 0xc5, 0x70, 0xaf, 0xe7, 0x89, 0xf7, 0xc3, - 0x83, 0x96, 0xc3, 0x06, 0x1b, 0x2e, 0x73, 0xfa, 0x34, 0xda, 0xe0, 0x1f, 0x48, 0x34, 0xe8, 0x7b, - 0x62, 0x83, 0x84, 0xde, 0x06, 0x3b, 0xf8, 0x03, 0x75, 0x04, 0x6f, 0x85, 0x11, 0x13, 0x0c, 0xa1, - 0x18, 0xd2, 0x4a, 0x20, 0xad, 0xd1, 0x8f, 0x6b, 0x77, 0x2f, 0x90, 0x20, 0xc6, 0x21, 0xd5, 0xfc, - 0x0b, 0xb1, 0x3c, 0xa4, 0x4e, 0x82, 0x6d, 0xf4, 0x18, 0xeb, 0xf9, 0x74, 0x43, 0xed, 0x0e, 0x86, - 0xef, 0x36, 0x84, 0x37, 0xa0, 0x5c, 0x90, 0x41, 0xa8, 0x01, 0x2b, 0x3d, 0xd6, 0x63, 0x6a, 0xb9, - 0x21, 0x57, 0xfa, 0xeb, 0xad, 0xd3, 0x34, 0x12, 0x8c, 0xf5, 0xd1, 0xcf, 0xcf, 0xd1, 0x3e, 0x85, - 0x87, 0xfe, 0xb0, 0xe7, 0x05, 0xfa, 0x4f, 0x4c, 0x6c, 0xfe, 0xd5, 0x00, 0xf3, 0x05, 0x15, 0x04, - 0xfd, 0x02, 0x8a, 0x23, 0x1a, 0x71, 0x8f, 0x05, 0x96, 0xb1, 0x6e, 0xdc, 0xa9, 0x6c, 0x7e, 0xaf, - 0x35, 0x1f, 0x91, 0xd6, 0x9b, 0x18, 0xd2, 0x31, 0x3f, 0x1f, 0x37, 0x6e, 0xe0, 0x84, 0x81, 0x1e, - 0x02, 0x38, 0x11, 0x25, 0x82, 0xba, 0x36, 0x11, 0x56, 0x4e, 0xf1, 0x6b, 0xad, 0xd8, 0xdc, 0x56, - 0xa2, 0xbf, 0xf5, 0x3a, 0xf1, 0x12, 0x97, 0x35, 0xba, 0x2d, 0x24, 0x75, 0x18, 0xba, 0x09, 0x35, - 0x7f, 0x31, 0x55, 0xa3, 0xdb, 0xa2, 0xf9, 0x71, 0x01, 0xcc, 0xdf, 0x30, 0x97, 0xa2, 0x55, 0xc8, - 0x79, 0xae, 0x32, 0xbb, 0xdc, 0x29, 0x4c, 0x8e, 0x1b, 0xb9, 0xee, 0x2e, 0xce, 0x79, 0x2e, 0xda, - 0x04, 0x73, 0x40, 0x05, 0xd1, 0x06, 0x59, 0x59, 0x0e, 0x49, 0xdf, 0xb5, 0x37, 0x0a, 0x8b, 0x7e, - 0x06, 0xa6, 0xbc, 0x2a, 0x6d, 0xc9, 0x5a, 0x16, 0x47, 0xea, 0x7c, 0x15, 0x52, 0x27, 0xe1, 0x49, - 0x3c, 0xda, 0x83, 0x8a, 0x4b, 0xb9, 0x13, 0x79, 0xa1, 0x90, 0x31, 0x34, 0x15, 0xfd, 0xf6, 0x59, - 0xf4, 0xdd, 0x19, 0x14, 0xa7, 0x79, 0xe8, 0x97, 0x50, 0xe0, 0x82, 0x88, 0x21, 0xb7, 0x16, 0x94, - 0x84, 0xfa, 0x99, 0x06, 0x28, 0x94, 0x36, 0x41, 0x73, 0xd0, 0x33, 0xa8, 0x0e, 0x48, 0x40, 0x7a, - 0x34, 0xb2, 0xb5, 0x94, 0x82, 0x92, 0xf2, 0x83, 0x4c, 0xd7, 0x63, 0x64, 0x2c, 0x08, 0x2f, 0x0d, - 0xd2, 0x5b, 0xd4, 0x05, 0x20, 0x42, 0x10, 0xe7, 0xfd, 0x80, 0x06, 0xc2, 0x2a, 0x2a, 0x29, 0x3f, - 0xcc, 0xb4, 0x85, 0x8a, 0x0f, 0x2c, 0xea, 0xb7, 0xa7, 0xe0, 0x4e, 0xce, 0x32, 0x70, 0x8a, 0x8c, - 0x9e, 0x42, 0xc5, 0xa1, 0x91, 0xf0, 0xde, 0x79, 0x0e, 0x11, 0xd4, 0x2a, 0x29, 0x59, 0x8d, 0x2c, - 0x59, 0x3b, 0x33, 0x98, 0x76, 0x2c, 0xcd, 0x44, 0xf7, 0xc1, 0x8c, 0x98, 0x4f, 0xad, 0xf2, 0xba, - 0x71, 0xa7, 0x7a, 0xf6, 0xd5, 0x60, 0xe6, 0x53, 0xac, 0x90, 0x52, 0xf5, 0xcc, 0x10, 0x6e, 0xc1, - 0x7a, 0xfe, 0xd2, 0x6e, 0xe0, 0x34, 0x73, 0x7b, 0xf5, 0xf0, 0xa8, 0x89, 0x60, 0xb9, 0x64, 0x2c, - 0x1b, 0x2a, 0xcf, 0x8c, 0xfb, 0xc6, 0xef, 0x8c, 0xdf, 0x1b, 0xcd, 0xff, 0xe6, 0xa1, 0xf8, 0x8a, - 0x46, 0x23, 0xcf, 0xf9, 0xba, 0x59, 0xf8, 0xf0, 0x44, 0x16, 0x66, 0x06, 0x4b, 0xab, 0x9d, 0x4b, - 0xc4, 0x2d, 0x28, 0xd1, 0xc0, 0x0d, 0x99, 0x17, 0x08, 0x9d, 0x85, 0x99, 0x91, 0xda, 0xd3, 0x18, - 0x3c, 0x45, 0xa3, 0x3d, 0x58, 0x8a, 0x8b, 0xcb, 0x3e, 0x91, 0x82, 0xeb, 0x59, 0xf4, 0xdf, 0x2a, - 0xa0, 0xce, 0x9d, 0xc5, 0x61, 0x6a, 0x87, 0x76, 0x61, 0x29, 0x8c, 0xe8, 0xc8, 0x63, 0x43, 0x6e, - 0x2b, 0x27, 0x0a, 0x97, 0x72, 0x02, 0x2f, 0x26, 0x2c, 0xb9, 0x43, 0xbf, 0x82, 0x45, 0x49, 0xb6, - 0x93, 0xa6, 0x04, 0x17, 0x36, 0x25, 0x5c, 0x91, 0x04, 0xbd, 0x41, 0x2f, 0xe1, 0x3b, 0x27, 0xac, - 0x98, 0x0a, 0xaa, 0x5c, 0x2c, 0xe8, 0x66, 0xda, 0x12, 0xfd, 0x71, 0x1b, 0x1d, 0x1e, 0x35, 0xab, - 0xb0, 0x98, 0x4e, 0x81, 0xe6, 0x9f, 0x73, 0x50, 0x4a, 0x02, 0x89, 0x1e, 0xe8, 0x3b, 0x33, 0xce, - 0x8e, 0x5a, 0x82, 0x55, 0xfe, 0xc6, 0xd7, 0xf5, 0x00, 0x16, 0x42, 0x16, 0x09, 0x6e, 0xe5, 0x54, - 0x72, 0x66, 0xd6, 0xfb, 0x3e, 0x8b, 0xc4, 0x0e, 0x0b, 0xde, 0x79, 0x3d, 0x1c, 0x83, 0xd1, 0x5b, - 0xa8, 0x8c, 0xbc, 0x48, 0x0c, 0x89, 0x6f, 0x7b, 0x21, 0xb7, 0xf2, 0x8a, 0xfb, 0xa3, 0xf3, 0x54, - 0xb6, 0xde, 0xc4, 0xf8, 0xee, 0x7e, 0xa7, 0x3a, 0x39, 0x6e, 0xc0, 0x74, 0xcb, 0x31, 0x68, 0x51, - 0xdd, 0x90, 0xd7, 0x5e, 0x40, 0x79, 0x7a, 0x82, 0xee, 0x01, 0x04, 0x71, 0x5d, 0xd8, 0xd3, 0xcc, - 0x5e, 0x9a, 0x1c, 0x37, 0xca, 0xba, 0x5a, 0xba, 0xbb, 0xb8, 0xac, 0x01, 0x5d, 0x17, 0x21, 0x30, - 0x89, 0xeb, 0x46, 0x2a, 0xcf, 0xcb, 0x58, 0xad, 0x9b, 0x7f, 0x2a, 0x82, 0xf9, 0x9a, 0xf0, 0xfe, - 0x75, 0xb7, 0x68, 0xa9, 0x73, 0xae, 0x32, 0xee, 0x01, 0xf0, 0x38, 0xdf, 0xa4, 0x3b, 0xe6, 0xcc, - 0x1d, 0x9d, 0x85, 0xd2, 0x1d, 0x0d, 0x88, 0xdd, 0xe1, 0x3e, 0x13, 0xaa, 0x08, 0x4c, 0xac, 0xd6, - 0xe8, 0x36, 0x14, 0x03, 0xe6, 0x2a, 0x7a, 0x41, 0xd1, 0x61, 0x72, 0xdc, 0x28, 0xc8, 0xa6, 0xd3, - 0xdd, 0xc5, 0x05, 0x79, 0xd4, 0x75, 0x55, 0xd3, 0x09, 0x02, 0x26, 0x88, 0x6c, 0xe8, 0x5c, 0xf7, - 0xce, 0xcc, 0xec, 0x6f, 0xcf, 0x60, 0x49, 0xbf, 0x4b, 0x31, 0xd1, 0x1b, 0xb8, 0x99, 0xd8, 0x9b, - 0x16, 0x58, 0xba, 0x8a, 0x40, 0xa4, 0x25, 0xa4, 0x4e, 0x52, 0x6f, 0x4c, 0xf9, 0xec, 0x37, 0x46, - 0x45, 0x30, 0xeb, 0x8d, 0xe9, 0xc0, 0x92, 0x4b, 0xb9, 0x17, 0x51, 0x57, 0xb5, 0x09, 0xaa, 0x2a, - 0xb3, 0xba, 0xf9, 0xfd, 0xf3, 0x84, 0x50, 0xbc, 0xa8, 0x39, 0x6a, 0x87, 0xda, 0x50, 0xd2, 0x79, - 0xc3, 0xad, 0xca, 0x55, 0x9a, 0xf2, 0x94, 0x76, 0xa2, 0xcd, 0x2d, 0x5e, 0xa9, 0xcd, 0x3d, 0x04, - 0xf0, 0x59, 0xcf, 0x76, 0x23, 0x6f, 0x44, 0x23, 0x6b, 0x49, 0x4f, 0x1c, 0x19, 0xdc, 0x5d, 0x85, - 0xc0, 0x65, 0x9f, 0xf5, 0xe2, 0xe5, 0x5c, 0x53, 0xaa, 0x5e, 0xb1, 0x29, 0x11, 0xa8, 0x11, 0xce, - 0xbd, 0x5e, 0x40, 0x5d, 0xbb, 0x47, 0x03, 0x1a, 0x79, 0x8e, 0x1d, 0x51, 0xce, 0x86, 0x91, 0x43, - 0xb9, 0xf5, 0x2d, 0x15, 0x89, 0xcc, 0x99, 0xe1, 0x69, 0x0c, 0xc6, 0x1a, 0x8b, 0xad, 0x44, 0xcc, - 0xa9, 0x03, 0xbe, 0x5d, 0x3b, 0x3c, 0x6a, 0xae, 0xc2, 0x4a, 0xba, 0x4d, 0x6d, 0x19, 0x4f, 0x8c, - 0x67, 0xc6, 0xbe, 0xd1, 0xfc, 0x7b, 0x0e, 0xbe, 0x3d, 0x17, 0x53, 0xf4, 0x53, 0x28, 0xea, 0xa8, - 0x9e, 0x37, 0xf9, 0x69, 0x1e, 0x4e, 0xb0, 0x68, 0x0d, 0xca, 0xb2, 0xc4, 0x29, 0xe7, 0x34, 0x6e, - 0x5e, 0x65, 0x3c, 0xfb, 0x80, 0x2c, 0x28, 0x12, 0xdf, 0x23, 0xf2, 0x2c, 0xaf, 0xce, 0x92, 0x2d, - 0x1a, 0xc2, 0x6a, 0x1c, 0x7a, 0x7b, 0xf6, 0xc0, 0xda, 0x2c, 0x14, 0xdc, 0x32, 0x95, 0xff, 0x8f, - 0x2f, 0x95, 0x09, 0xfa, 0x72, 0x66, 0x1f, 0x5e, 0x86, 0x82, 0xef, 0x05, 0x22, 0x1a, 0xe3, 0x15, - 0x37, 0xe3, 0xa8, 0xf6, 0x14, 0x6e, 0x9d, 0x49, 0x41, 0xcb, 0x90, 0xef, 0xd3, 0x71, 0xdc, 0x9e, - 0xb0, 0x5c, 0xa2, 0x15, 0x58, 0x18, 0x11, 0x7f, 0x48, 0x75, 0x37, 0x8b, 0x37, 0xdb, 0xb9, 0x2d, - 0xa3, 0xf9, 0x29, 0x07, 0x45, 0x6d, 0xce, 0x75, 0x3f, 0xf9, 0x5a, 0xed, 0x5c, 0x63, 0x7b, 0x04, - 0x8b, 0x3a, 0xa4, 0x71, 0x45, 0x9a, 0x17, 0xe6, 0x74, 0x25, 0xc6, 0xc7, 0xd5, 0xf8, 0x08, 0x4c, - 0x2f, 0x24, 0x03, 0xfd, 0xdc, 0x67, 0x6a, 0xee, 0xee, 0xb7, 0x5f, 0xbc, 0x0c, 0xe3, 0xc6, 0x52, - 0x9a, 0x1c, 0x37, 0x4c, 0xf9, 0x01, 0x2b, 0x5a, 0xe6, 0xc3, 0xf8, 0x97, 0x05, 0x28, 0xee, 0xf8, - 0x43, 0x2e, 0x68, 0x74, 0xdd, 0x41, 0xd2, 0x6a, 0xe7, 0x82, 0xb4, 0x03, 0xc5, 0x88, 0x31, 0x61, - 0x3b, 0xe4, 0xbc, 0xf8, 0x60, 0xc6, 0xc4, 0x4e, 0xbb, 0x53, 0x95, 0x44, 0xd9, 0xdb, 0xe3, 0x3d, - 0x2e, 0x48, 0xea, 0x0e, 0x41, 0x6f, 0x61, 0x35, 0x79, 0x11, 0x0f, 0x18, 0x13, 0x5c, 0x44, 0x24, - 0xb4, 0xfb, 0x74, 0x2c, 0x67, 0xa5, 0xfc, 0x59, 0x83, 0xf6, 0x5e, 0xe0, 0x44, 0x63, 0x15, 0xbc, - 0xe7, 0x74, 0x8c, 0x57, 0xb4, 0x80, 0x4e, 0xc2, 0x7f, 0x4e, 0xc7, 0x1c, 0x3d, 0x86, 0x35, 0x3a, - 0x85, 0x49, 0x89, 0xb6, 0x4f, 0x06, 0xf2, 0xad, 0xb7, 0x1d, 0x9f, 0x39, 0x7d, 0xf5, 0xdc, 0x98, - 0xf8, 0x16, 0x4d, 0x8b, 0xfa, 0x75, 0x8c, 0xd8, 0x91, 0x00, 0xc4, 0xc1, 0x3a, 0xf0, 0x89, 0xd3, - 0xf7, 0x3d, 0x2e, 0xff, 0x97, 0x4a, 0xcd, 0xcd, 0xf2, 0xc5, 0x90, 0xb6, 0x6d, 0x9d, 0x13, 0xad, - 0x56, 0x67, 0xc6, 0x4d, 0x4d, 0xe1, 0xba, 0xa2, 0xbe, 0x7b, 0x90, 0x7d, 0x8a, 0x3a, 0x50, 0x19, - 0x06, 0x52, 0x7d, 0x1c, 0x83, 0xf2, 0x65, 0x63, 0x00, 0x31, 0x4b, 0x7a, 0x5e, 0x1b, 0xc1, 0xda, - 0x79, 0xca, 0x33, 0x6a, 0xf3, 0x49, 0xba, 0x36, 0x2b, 0x9b, 0x77, 0xb3, 0xf4, 0x65, 0x8b, 0x4c, - 0xd5, 0x71, 0x66, 0xda, 0xfe, 0xcd, 0x80, 0xc2, 0x2b, 0xea, 0x44, 0x54, 0x7c, 0xd5, 0xac, 0xdd, - 0x3a, 0x91, 0xb5, 0xf5, 0xec, 0x41, 0x58, 0x6a, 0x9d, 0x4b, 0xda, 0x1a, 0x94, 0xbc, 0x40, 0xd0, - 0x28, 0x20, 0xbe, 0xca, 0xda, 0x12, 0x9e, 0xee, 0x33, 0x1d, 0xf8, 0x64, 0x40, 0x21, 0x9e, 0x14, - 0xaf, 0xdb, 0x81, 0x58, 0xeb, 0x69, 0x07, 0x32, 0x8d, 0xfc, 0x8f, 0x01, 0xa5, 0xe4, 0xc1, 0xfa, - 0xaa, 0x66, 0x9e, 0x9a, 0xbc, 0xf2, 0xff, 0xf7, 0xe4, 0x85, 0xc0, 0xec, 0x7b, 0x81, 0x9e, 0x11, - 0xb1, 0x5a, 0xa3, 0x16, 0x14, 0x43, 0x32, 0xf6, 0x19, 0x71, 0x75, 0xa3, 0x5c, 0x99, 0xfb, 0x95, - 0xa2, 0x1d, 0x8c, 0x71, 0x02, 0xda, 0x5e, 0x39, 0x3c, 0x6a, 0x2e, 0x43, 0x35, 0xed, 0xf9, 0x7b, - 0xa3, 0xf9, 0x4f, 0x03, 0xca, 0x7b, 0x7f, 0x14, 0x34, 0x50, 0xf3, 0xc0, 0x37, 0xd2, 0xf9, 0xf5, - 0xf9, 0x5f, 0x32, 0xca, 0x27, 0x7e, 0xa4, 0xc8, 0xba, 0xd4, 0x8e, 0xf5, 0xf9, 0x4b, 0xfd, 0xc6, - 0xbf, 0xbe, 0xd4, 0x6f, 0x7c, 0x9c, 0xd4, 0x8d, 0xcf, 0x93, 0xba, 0xf1, 0x8f, 0x49, 0xdd, 0xf8, - 0xf7, 0xa4, 0x6e, 0x1c, 0x14, 0x54, 0x7c, 0x7e, 0xf2, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf9, - 0xa1, 0x26, 0x54, 0x90, 0x13, 0x00, 0x00, + // 1544 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4d, 0x73, 0xdb, 0x4c, + 0x1d, 0xaf, 0x6c, 0xc5, 0x2f, 0x7f, 0x27, 0x26, 0xec, 0x13, 0x82, 0x6a, 0x82, 0x1d, 0xfc, 0x0c, + 0xcc, 0x33, 0xcf, 0x74, 0x9c, 0x12, 0x0a, 0xa4, 0x81, 0xd2, 0xda, 0x49, 0x68, 0x3d, 0xa5, 0x34, + 0xb3, 0x29, 0x2d, 0x37, 0xb1, 0x91, 0x36, 0xae, 0xb0, 0xac, 0xd5, 0x68, 0xd7, 0x2e, 0xbe, 0xf5, + 0x1c, 0x3e, 0x40, 0x6e, 0x1c, 0xfa, 0x2d, 0xb8, 0x70, 0xe0, 0xd4, 0x23, 0xc3, 0x81, 0xe1, 0x94, + 0xa1, 0xfe, 0x16, 0xcc, 0x70, 0x60, 0x76, 0xb5, 0xb2, 0x95, 0x58, 0x79, 0x63, 0x3a, 0x19, 0x4e, + 0xd1, 0x6a, 0x7f, 0xbf, 0xff, 0x9b, 0xfe, 0x6f, 0x31, 0xdc, 0xeb, 0x79, 0xe2, 0xed, 0xf0, 0xb0, + 0xe5, 0xb0, 0xc1, 0x86, 0xcb, 0x9c, 0x3e, 0x8d, 0x36, 0xf8, 0x3b, 0x12, 0x0d, 0xfa, 0x9e, 0xd8, + 0x20, 0xa1, 0xb7, 0xc1, 0x0e, 0x7f, 0x4f, 0x1d, 0xc1, 0x5b, 0x61, 0xc4, 0x04, 0x43, 0x28, 0x86, + 0xb4, 0x12, 0x48, 0x6b, 0xf4, 0xc3, 0xda, 0xd7, 0x57, 0x48, 0x10, 0xe3, 0x90, 0x6a, 0xfe, 0x95, + 0x58, 0x1e, 0x52, 0x27, 0xc1, 0x36, 0x7a, 0x8c, 0xf5, 0x7c, 0xba, 0xa1, 0x4e, 0x87, 0xc3, 0xa3, + 0x0d, 0xe1, 0x0d, 0x28, 0x17, 0x64, 0x10, 0x6a, 0xc0, 0x4a, 0x8f, 0xf5, 0x98, 0x7a, 0xdc, 0x90, + 0x4f, 0xfa, 0xed, 0xdd, 0xf3, 0x34, 0x12, 0x8c, 0xf5, 0xd5, 0x4f, 0x2f, 0xd1, 0x3e, 0x85, 0x87, + 0xfe, 0xb0, 0xe7, 0x05, 0xfa, 0x4f, 0x4c, 0x6c, 0xfe, 0xd9, 0x00, 0xf3, 0x05, 0x15, 0x04, 0xfd, + 0x0c, 0x8a, 0x23, 0x1a, 0x71, 0x8f, 0x05, 0x96, 0xb1, 0x6e, 0x7c, 0x55, 0xd9, 0xfc, 0x4e, 0x6b, + 0x3e, 0x22, 0xad, 0xd7, 0x31, 0xa4, 0x63, 0x7e, 0x3c, 0x6d, 0xdc, 0xc1, 0x09, 0x03, 0x3d, 0x04, + 0x70, 0x22, 0x4a, 0x04, 0x75, 0x6d, 0x22, 0xac, 0x9c, 0xe2, 0xd7, 0x5a, 0xb1, 0xb9, 0xad, 0x44, + 0x7f, 0xeb, 0x55, 0xe2, 0x25, 0x2e, 0x6b, 0x74, 0x5b, 0x48, 0xea, 0x30, 0x74, 0x13, 0x6a, 0xfe, + 0x6a, 0xaa, 0x46, 0xb7, 0x45, 0xf3, 0xfd, 0x02, 0x98, 0xbf, 0x66, 0x2e, 0x45, 0xab, 0x90, 0xf3, + 0x5c, 0x65, 0x76, 0xb9, 0x53, 0x98, 0x9c, 0x36, 0x72, 0xdd, 0x5d, 0x9c, 0xf3, 0x5c, 0xb4, 0x09, + 0xe6, 0x80, 0x0a, 0xa2, 0x0d, 0xb2, 0xb2, 0x1c, 0x92, 0xbe, 0x6b, 0x6f, 0x14, 0x16, 0xfd, 0x04, + 0x4c, 0xf9, 0xa9, 0xb4, 0x25, 0x6b, 0x59, 0x1c, 0xa9, 0xf3, 0x20, 0xa4, 0x4e, 0xc2, 0x93, 0x78, + 0xb4, 0x07, 0x15, 0x97, 0x72, 0x27, 0xf2, 0x42, 0x21, 0x63, 0x68, 0x2a, 0xfa, 0x97, 0x17, 0xd1, + 0x77, 0x67, 0x50, 0x9c, 0xe6, 0xa1, 0x9f, 0x43, 0x81, 0x0b, 0x22, 0x86, 0xdc, 0x5a, 0x50, 0x12, + 0xea, 0x17, 0x1a, 0xa0, 0x50, 0xda, 0x04, 0xcd, 0x41, 0xcf, 0xa0, 0x3a, 0x20, 0x01, 0xe9, 0xd1, + 0xc8, 0xd6, 0x52, 0x0a, 0x4a, 0xca, 0xf7, 0x32, 0x5d, 0x8f, 0x91, 0xb1, 0x20, 0xbc, 0x34, 0x48, + 0x1f, 0x51, 0x17, 0x80, 0x08, 0x41, 0x9c, 0xb7, 0x03, 0x1a, 0x08, 0xab, 0xa8, 0xa4, 0x7c, 0x3f, + 0xd3, 0x16, 0x2a, 0xde, 0xb1, 0xa8, 0xdf, 0x9e, 0x82, 0x3b, 0x39, 0xcb, 0xc0, 0x29, 0x32, 0x7a, + 0x0a, 0x15, 0x87, 0x46, 0xc2, 0x3b, 0xf2, 0x1c, 0x22, 0xa8, 0x55, 0x52, 0xb2, 0x1a, 0x59, 0xb2, + 0x76, 0x66, 0x30, 0xed, 0x58, 0x9a, 0x89, 0xee, 0x83, 0x19, 0x31, 0x9f, 0x5a, 0xe5, 0x75, 0xe3, + 0xab, 0xea, 0xc5, 0x9f, 0x06, 0x33, 0x9f, 0x62, 0x85, 0x94, 0xaa, 0x67, 0x86, 0x70, 0x0b, 0xd6, + 0xf3, 0xd7, 0x76, 0x03, 0xa7, 0x99, 0xdb, 0xab, 0xc7, 0x27, 0x4d, 0x04, 0xcb, 0x25, 0x63, 0xd9, + 0x50, 0x79, 0x66, 0xdc, 0x37, 0x7e, 0x6b, 0xfc, 0xce, 0x68, 0xfe, 0x27, 0x0f, 0xc5, 0x03, 0x1a, + 0x8d, 0x3c, 0xe7, 0xf3, 0x66, 0xe1, 0xc3, 0x33, 0x59, 0x98, 0x19, 0x2c, 0xad, 0x76, 0x2e, 0x11, + 0xb7, 0xa0, 0x44, 0x03, 0x37, 0x64, 0x5e, 0x20, 0x74, 0x16, 0x66, 0x46, 0x6a, 0x4f, 0x63, 0xf0, + 0x14, 0x8d, 0xf6, 0x60, 0x29, 0x2e, 0x2e, 0xfb, 0x4c, 0x0a, 0xae, 0x67, 0xd1, 0x7f, 0xa3, 0x80, + 0x3a, 0x77, 0x16, 0x87, 0xa9, 0x13, 0xda, 0x85, 0xa5, 0x30, 0xa2, 0x23, 0x8f, 0x0d, 0xb9, 0xad, + 0x9c, 0x28, 0x5c, 0xcb, 0x09, 0xbc, 0x98, 0xb0, 0xe4, 0x09, 0xfd, 0x02, 0x16, 0x25, 0xd9, 0x4e, + 0x9a, 0x12, 0x5c, 0xd9, 0x94, 0x70, 0x45, 0x12, 0xf4, 0x01, 0xbd, 0x84, 0x6f, 0x9d, 0xb1, 0x62, + 0x2a, 0xa8, 0x72, 0xb5, 0xa0, 0x2f, 0xd2, 0x96, 0xe8, 0x97, 0xdb, 0xe8, 0xf8, 0xa4, 0x59, 0x85, + 0xc5, 0x74, 0x0a, 0x34, 0xff, 0x94, 0x83, 0x52, 0x12, 0x48, 0xf4, 0x40, 0x7f, 0x33, 0xe3, 0xe2, + 0xa8, 0x25, 0x58, 0xe5, 0x6f, 0xfc, 0xb9, 0x1e, 0xc0, 0x42, 0xc8, 0x22, 0xc1, 0xad, 0x9c, 0x4a, + 0xce, 0xcc, 0x7a, 0xdf, 0x67, 0x91, 0xd8, 0x61, 0xc1, 0x91, 0xd7, 0xc3, 0x31, 0x18, 0xbd, 0x81, + 0xca, 0xc8, 0x8b, 0xc4, 0x90, 0xf8, 0xb6, 0x17, 0x72, 0x2b, 0xaf, 0xb8, 0x3f, 0xb8, 0x4c, 0x65, + 0xeb, 0x75, 0x8c, 0xef, 0xee, 0x77, 0xaa, 0x93, 0xd3, 0x06, 0x4c, 0x8f, 0x1c, 0x83, 0x16, 0xd5, + 0x0d, 0x79, 0xed, 0x05, 0x94, 0xa7, 0x37, 0xe8, 0x1e, 0x40, 0x10, 0xd7, 0x85, 0x3d, 0xcd, 0xec, + 0xa5, 0xc9, 0x69, 0xa3, 0xac, 0xab, 0xa5, 0xbb, 0x8b, 0xcb, 0x1a, 0xd0, 0x75, 0x11, 0x02, 0x93, + 0xb8, 0x6e, 0xa4, 0xf2, 0xbc, 0x8c, 0xd5, 0x73, 0xf3, 0x8f, 0x45, 0x30, 0x5f, 0x11, 0xde, 0xbf, + 0xed, 0x16, 0x2d, 0x75, 0xce, 0x55, 0xc6, 0x3d, 0x00, 0x1e, 0xe7, 0x9b, 0x74, 0xc7, 0x9c, 0xb9, + 0xa3, 0xb3, 0x50, 0xba, 0xa3, 0x01, 0xb1, 0x3b, 0xdc, 0x67, 0x42, 0x15, 0x81, 0x89, 0xd5, 0x33, + 0xfa, 0x12, 0x8a, 0x01, 0x73, 0x15, 0xbd, 0xa0, 0xe8, 0x30, 0x39, 0x6d, 0x14, 0x64, 0xd3, 0xe9, + 0xee, 0xe2, 0x82, 0xbc, 0xea, 0xba, 0xaa, 0xe9, 0x04, 0x01, 0x13, 0x44, 0x36, 0x74, 0xae, 0x7b, + 0x67, 0x66, 0xf6, 0xb7, 0x67, 0xb0, 0xa4, 0xdf, 0xa5, 0x98, 0xe8, 0x35, 0x7c, 0x91, 0xd8, 0x9b, + 0x16, 0x58, 0xba, 0x89, 0x40, 0xa4, 0x25, 0xa4, 0x6e, 0x52, 0x33, 0xa6, 0x7c, 0xf1, 0x8c, 0x51, + 0x11, 0xcc, 0x9a, 0x31, 0x1d, 0x58, 0x72, 0x29, 0xf7, 0x22, 0xea, 0xaa, 0x36, 0x41, 0x55, 0x65, + 0x56, 0x37, 0xbf, 0x7b, 0x99, 0x10, 0x8a, 0x17, 0x35, 0x47, 0x9d, 0x50, 0x1b, 0x4a, 0x3a, 0x6f, + 0xb8, 0x55, 0xb9, 0x49, 0x53, 0x9e, 0xd2, 0xce, 0xb4, 0xb9, 0xc5, 0x1b, 0xb5, 0xb9, 0x87, 0x00, + 0x3e, 0xeb, 0xd9, 0x6e, 0xe4, 0x8d, 0x68, 0x64, 0x2d, 0xe9, 0x8d, 0x23, 0x83, 0xbb, 0xab, 0x10, + 0xb8, 0xec, 0xb3, 0x5e, 0xfc, 0x38, 0xd7, 0x94, 0xaa, 0x37, 0x6c, 0x4a, 0x04, 0x6a, 0x84, 0x73, + 0xaf, 0x17, 0x50, 0xd7, 0xee, 0xd1, 0x80, 0x46, 0x9e, 0x63, 0x47, 0x94, 0xb3, 0x61, 0xe4, 0x50, + 0x6e, 0x7d, 0x43, 0x45, 0x22, 0x73, 0x67, 0x78, 0x1a, 0x83, 0xb1, 0xc6, 0x62, 0x2b, 0x11, 0x73, + 0xee, 0x82, 0x6f, 0xd7, 0x8e, 0x4f, 0x9a, 0xab, 0xb0, 0x92, 0x6e, 0x53, 0x5b, 0xc6, 0x13, 0xe3, + 0x99, 0xb1, 0x6f, 0x34, 0xff, 0x9a, 0x83, 0x6f, 0xce, 0xc5, 0x14, 0xfd, 0x18, 0x8a, 0x3a, 0xaa, + 0x97, 0x6d, 0x7e, 0x9a, 0x87, 0x13, 0x2c, 0x5a, 0x83, 0xb2, 0x2c, 0x71, 0xca, 0x39, 0x8d, 0x9b, + 0x57, 0x19, 0xcf, 0x5e, 0x20, 0x0b, 0x8a, 0xc4, 0xf7, 0x88, 0xbc, 0xcb, 0xab, 0xbb, 0xe4, 0x88, + 0x86, 0xb0, 0x1a, 0x87, 0xde, 0x9e, 0x0d, 0x58, 0x9b, 0x85, 0x82, 0x5b, 0xa6, 0xf2, 0xff, 0xf1, + 0xb5, 0x32, 0x41, 0x7f, 0x9c, 0xd9, 0x8b, 0x97, 0xa1, 0xe0, 0x7b, 0x81, 0x88, 0xc6, 0x78, 0xc5, + 0xcd, 0xb8, 0xaa, 0x3d, 0x85, 0xbb, 0x17, 0x52, 0xd0, 0x32, 0xe4, 0xfb, 0x74, 0x1c, 0xb7, 0x27, + 0x2c, 0x1f, 0xd1, 0x0a, 0x2c, 0x8c, 0x88, 0x3f, 0xa4, 0xba, 0x9b, 0xc5, 0x87, 0xed, 0xdc, 0x96, + 0xd1, 0xfc, 0x90, 0x83, 0xa2, 0x36, 0xe7, 0xb6, 0x47, 0xbe, 0x56, 0x3b, 0xd7, 0xd8, 0x1e, 0xc1, + 0xa2, 0x0e, 0x69, 0x5c, 0x91, 0xe6, 0x95, 0x39, 0x5d, 0x89, 0xf1, 0x71, 0x35, 0x3e, 0x02, 0xd3, + 0x0b, 0xc9, 0x40, 0x8f, 0xfb, 0x4c, 0xcd, 0xdd, 0xfd, 0xf6, 0x8b, 0x97, 0x61, 0xdc, 0x58, 0x4a, + 0x93, 0xd3, 0x86, 0x29, 0x5f, 0x60, 0x45, 0xcb, 0x1c, 0x8c, 0x7f, 0x5f, 0x80, 0xe2, 0x8e, 0x3f, + 0xe4, 0x82, 0x46, 0xb7, 0x1d, 0x24, 0xad, 0x76, 0x2e, 0x48, 0x3b, 0x50, 0x8c, 0x18, 0x13, 0xb6, + 0x43, 0x2e, 0x8b, 0x0f, 0x66, 0x4c, 0xec, 0xb4, 0x3b, 0x55, 0x49, 0x94, 0xbd, 0x3d, 0x3e, 0xe3, + 0x82, 0xa4, 0xee, 0x10, 0xf4, 0x06, 0x56, 0x93, 0x89, 0x78, 0xc8, 0x98, 0xe0, 0x22, 0x22, 0xa1, + 0xdd, 0xa7, 0x63, 0xb9, 0x2b, 0xe5, 0x2f, 0x5a, 0xb4, 0xf7, 0x02, 0x27, 0x1a, 0xab, 0xe0, 0x3d, + 0xa7, 0x63, 0xbc, 0xa2, 0x05, 0x74, 0x12, 0xfe, 0x73, 0x3a, 0xe6, 0xe8, 0x31, 0xac, 0xd1, 0x29, + 0x4c, 0x4a, 0xb4, 0x7d, 0x32, 0x90, 0xb3, 0xde, 0x76, 0x7c, 0xe6, 0xf4, 0xd5, 0xb8, 0x31, 0xf1, + 0x5d, 0x9a, 0x16, 0xf5, 0xab, 0x18, 0xb1, 0x23, 0x01, 0x88, 0x83, 0x75, 0xe8, 0x13, 0xa7, 0xef, + 0x7b, 0x5c, 0xfe, 0x2f, 0x95, 0xda, 0x9b, 0xe5, 0xc4, 0x90, 0xb6, 0x6d, 0x5d, 0x12, 0xad, 0x56, + 0x67, 0xc6, 0x4d, 0x6d, 0xe1, 0xba, 0xa2, 0xbe, 0x7d, 0x98, 0x7d, 0x8b, 0x3a, 0x50, 0x19, 0x06, + 0x52, 0x7d, 0x1c, 0x83, 0xf2, 0x75, 0x63, 0x00, 0x31, 0x4b, 0x79, 0xbe, 0x06, 0xe6, 0x91, 0xdc, + 0x61, 0xe4, 0x18, 0x29, 0xc5, 0xc9, 0xf5, 0xcb, 0xee, 0xfe, 0x01, 0x56, 0x6f, 0x6b, 0x23, 0x58, + 0xbb, 0xcc, 0xb4, 0x8c, 0xca, 0x7d, 0x92, 0xae, 0xdc, 0xca, 0xe6, 0xd7, 0x59, 0xd6, 0x64, 0x8b, + 0x4c, 0x55, 0x79, 0x66, 0x52, 0xff, 0xc5, 0x80, 0xc2, 0x01, 0x75, 0x22, 0x2a, 0x3e, 0x6b, 0x4e, + 0x6f, 0x9d, 0xc9, 0xe9, 0x7a, 0xf6, 0x9a, 0x2c, 0xb5, 0xce, 0xa5, 0x74, 0x0d, 0x4a, 0x5e, 0x20, + 0x68, 0x14, 0x10, 0x5f, 0xe5, 0x74, 0x09, 0x4f, 0xcf, 0x99, 0x0e, 0x7c, 0x30, 0xa0, 0x10, 0xef, + 0x91, 0xb7, 0xed, 0x40, 0xac, 0xf5, 0xbc, 0x03, 0x99, 0x46, 0xfe, 0xdb, 0x80, 0x52, 0x32, 0xce, + 0x3e, 0xab, 0x99, 0xe7, 0xf6, 0xb2, 0xfc, 0xff, 0xbc, 0x97, 0x21, 0x30, 0xfb, 0x5e, 0xa0, 0x37, + 0x48, 0xac, 0x9e, 0x51, 0x0b, 0x8a, 0x21, 0x19, 0xfb, 0x8c, 0xb8, 0xba, 0x8d, 0xae, 0xcc, 0xfd, + 0x86, 0xd1, 0x0e, 0xc6, 0x38, 0x01, 0x6d, 0xaf, 0x1c, 0x9f, 0x34, 0x97, 0xa1, 0x9a, 0xf6, 0xfc, + 0xad, 0xd1, 0xfc, 0x87, 0x01, 0xe5, 0xbd, 0x3f, 0x08, 0x1a, 0xa8, 0x6d, 0xe1, 0xff, 0xd2, 0xf9, + 0xf5, 0xf9, 0xdf, 0x39, 0xca, 0x67, 0x7e, 0xc2, 0xc8, 0xfa, 0xa8, 0x1d, 0xeb, 0xe3, 0xa7, 0xfa, + 0x9d, 0x7f, 0x7e, 0xaa, 0xdf, 0x79, 0x3f, 0xa9, 0x1b, 0x1f, 0x27, 0x75, 0xe3, 0x6f, 0x93, 0xba, + 0xf1, 0xaf, 0x49, 0xdd, 0x38, 0x2c, 0xa8, 0xf8, 0xfc, 0xe8, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x07, 0xc5, 0x5a, 0x5b, 0xae, 0x13, 0x00, 0x00, } diff --git a/api/objects.proto b/api/objects.proto index c3ee4195b5..4ebdb26355 100644 --- a/api/objects.proto +++ b/api/objects.proto @@ -336,6 +336,12 @@ message Cluster { // If the key is empty, the node will be unlocked (will not require a key // to start up from a shut down state). repeated EncryptionKey unlock_keys = 9; + + // FIPS specifies whether this cluster should be in FIPS mode. This changes + // the format of the join tokens, and nodes that are not FIPS-enabled should + // reject joining the cluster. Nodes that report themselves to be non-FIPS + // should be rejected from the cluster. + bool fips = 10 [(gogoproto.customname) = "FIPS"]; } // Secret represents a secret that should be passed to a container or a node, diff --git a/api/types.pb.go b/api/types.pb.go index f7d0cdd27e..80ab4a646d 100644 --- a/api/types.pb.go +++ b/api/types.pb.go @@ -578,15 +578,18 @@ type MaybeEncryptedRecord_Algorithm int32 const ( MaybeEncryptedRecord_NotEncrypted MaybeEncryptedRecord_Algorithm = 0 MaybeEncryptedRecord_NACLSecretboxSalsa20Poly1305 MaybeEncryptedRecord_Algorithm = 1 + MaybeEncryptedRecord_FernetAES128CBC MaybeEncryptedRecord_Algorithm = 2 ) var MaybeEncryptedRecord_Algorithm_name = map[int32]string{ 0: "NONE", 1: "SECRETBOX_SALSA20_POLY1305", + 2: "FERNET_AES_128_CBC", } var MaybeEncryptedRecord_Algorithm_value = map[string]int32{ "NONE": 0, "SECRETBOX_SALSA20_POLY1305": 1, + "FERNET_AES_128_CBC": 2, } func (x MaybeEncryptedRecord_Algorithm) String() string { @@ -850,6 +853,8 @@ type NodeDescription struct { Engine *EngineDescription `protobuf:"bytes,4,opt,name=engine" json:"engine,omitempty"` // Information on the node's TLS setup TLSInfo *NodeTLSInfo `protobuf:"bytes,5,opt,name=tls_info,json=tlsInfo" json:"tls_info,omitempty"` + // FIPS indicates whether the node has FIPS-enabled + FIPS bool `protobuf:"varint,6,opt,name=fips,proto3" json:"fips,omitempty"` } func (m *NodeDescription) Reset() { *m = NodeDescription{} } @@ -4002,6 +4007,16 @@ func (m *NodeDescription) MarshalTo(dAtA []byte) (int, error) { } i += n9 } + if m.FIPS { + dAtA[i] = 0x30 + i++ + if m.FIPS { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i++ + } return i, nil } @@ -6361,6 +6376,9 @@ func (m *NodeDescription) Size() (n int) { l = m.TLSInfo.Size() n += 1 + l + sovTypes(uint64(l)) } + if m.FIPS { + n += 2 + } return n } @@ -7495,6 +7513,7 @@ func (this *NodeDescription) String() string { `Resources:` + strings.Replace(fmt.Sprintf("%v", this.Resources), "Resources", "Resources", 1) + `,`, `Engine:` + strings.Replace(fmt.Sprintf("%v", this.Engine), "EngineDescription", "EngineDescription", 1) + `,`, `TLSInfo:` + strings.Replace(fmt.Sprintf("%v", this.TLSInfo), "NodeTLSInfo", "NodeTLSInfo", 1) + `,`, + `FIPS:` + fmt.Sprintf("%v", this.FIPS) + `,`, `}`, }, "") return s @@ -9811,6 +9830,26 @@ func (m *NodeDescription) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FIPS", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FIPS = bool(v != 0) default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -17043,321 +17082,325 @@ var ( func init() { proto.RegisterFile("github.com/docker/swarmkit/api/types.proto", fileDescriptorTypes) } var fileDescriptorTypes = []byte{ - // 5054 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x5a, 0x4d, 0x6c, 0x24, 0x49, - 0x56, 0x76, 0xfd, 0xba, 0xea, 0x55, 0xd9, 0x4e, 0x47, 0x7b, 0x7b, 0xdc, 0xb5, 0xdd, 0x76, 0x4d, - 0xce, 0xf4, 0xce, 0x6c, 0x6f, 0x53, 0xfd, 0xb7, 0xbb, 0xea, 0x99, 0x61, 0x77, 0xa6, 0xfe, 0x6c, - 0xd7, 0xb6, 0x5d, 0x55, 0x8a, 0x2a, 0x77, 0xef, 0x22, 0x41, 0x92, 0xce, 0x0c, 0x97, 0x73, 0x9c, - 0x95, 0x51, 0x64, 0x66, 0xd9, 0x5d, 0x2c, 0x88, 0x16, 0x07, 0x40, 0x3e, 0xc1, 0x89, 0x45, 0xc8, - 0x08, 0x09, 0x8e, 0x48, 0x1c, 0x40, 0x42, 0x70, 0x1a, 0x24, 0x84, 0xf6, 0x06, 0x0b, 0x12, 0x5a, - 0x81, 0x64, 0x58, 0x1f, 0xb8, 0xad, 0xe0, 0x82, 0xb8, 0x80, 0x84, 0xe2, 0x27, 0xb3, 0xd2, 0xd5, - 0x69, 0xbb, 0x87, 0xdd, 0x8b, 0x5d, 0xf1, 0xde, 0xf7, 0x5e, 0x44, 0xbc, 0x88, 0x78, 0xf1, 0xde, - 0x8b, 0x84, 0x7b, 0x03, 0xcb, 0x3f, 0x18, 0xef, 0x55, 0x0c, 0x3a, 0x7c, 0x60, 0x52, 0xe3, 0x90, - 0xb8, 0x0f, 0xbc, 0x63, 0xdd, 0x1d, 0x1e, 0x5a, 0xfe, 0x03, 0x7d, 0x64, 0x3d, 0xf0, 0x27, 0x23, - 0xe2, 0x55, 0x46, 0x2e, 0xf5, 0x29, 0x42, 0x02, 0x50, 0x09, 0x00, 0x95, 0xa3, 0x47, 0xa5, 0xf5, - 0x01, 0xa5, 0x03, 0x9b, 0x3c, 0xe0, 0x88, 0xbd, 0xf1, 0xfe, 0x03, 0xdf, 0x1a, 0x12, 0xcf, 0xd7, - 0x87, 0x23, 0x21, 0x54, 0x5a, 0x9b, 0x05, 0x98, 0x63, 0x57, 0xf7, 0x2d, 0xea, 0x48, 0xfe, 0xca, - 0x80, 0x0e, 0x28, 0xff, 0xf9, 0x80, 0xfd, 0x12, 0x54, 0x75, 0x1d, 0xe6, 0x9f, 0x13, 0xd7, 0xb3, - 0xa8, 0x83, 0x56, 0x20, 0x63, 0x39, 0x26, 0x79, 0xb9, 0x9a, 0x28, 0x27, 0xde, 0x4f, 0x63, 0xd1, - 0x50, 0x1f, 0x02, 0xb4, 0xd8, 0x8f, 0xa6, 0xe3, 0xbb, 0x13, 0xa4, 0x40, 0xea, 0x90, 0x4c, 0x38, - 0x22, 0x8f, 0xd9, 0x4f, 0x46, 0x39, 0xd2, 0xed, 0xd5, 0xa4, 0xa0, 0x1c, 0xe9, 0xb6, 0xfa, 0xa3, - 0x04, 0x14, 0xaa, 0x8e, 0x43, 0x7d, 0xde, 0xbb, 0x87, 0x10, 0xa4, 0x1d, 0x7d, 0x48, 0xa4, 0x10, - 0xff, 0x8d, 0xea, 0x90, 0xb5, 0xf5, 0x3d, 0x62, 0x7b, 0xab, 0xc9, 0x72, 0xea, 0xfd, 0xc2, 0xe3, - 0xaf, 0x54, 0x5e, 0x9f, 0x72, 0x25, 0xa2, 0xa4, 0xb2, 0xcd, 0xd1, 0x7c, 0x10, 0x58, 0x8a, 0xa2, - 0x6f, 0xc2, 0xbc, 0xe5, 0x98, 0x96, 0x41, 0xbc, 0xd5, 0x34, 0xd7, 0xb2, 0x16, 0xa7, 0x65, 0x3a, - 0xfa, 0x5a, 0xfa, 0xfb, 0x67, 0xeb, 0x73, 0x38, 0x10, 0x2a, 0x7d, 0x00, 0x85, 0x88, 0xda, 0x98, - 0xb9, 0xad, 0x40, 0xe6, 0x48, 0xb7, 0xc7, 0x44, 0xce, 0x4e, 0x34, 0x3e, 0x4c, 0x3e, 0x4d, 0xa8, - 0x9f, 0xc0, 0x4a, 0x5b, 0x1f, 0x12, 0x73, 0x93, 0x38, 0xc4, 0xb5, 0x0c, 0x4c, 0x3c, 0x3a, 0x76, - 0x0d, 0xc2, 0xe6, 0x7a, 0x68, 0x39, 0x66, 0x30, 0x57, 0xf6, 0x3b, 0x5e, 0x8b, 0x5a, 0x87, 0xb7, - 0x1a, 0x96, 0x67, 0xb8, 0xc4, 0x27, 0x9f, 0x5b, 0x49, 0x2a, 0x50, 0x72, 0x96, 0x80, 0xa5, 0x59, - 0xe9, 0x9f, 0x83, 0x1b, 0xcc, 0xc4, 0xa6, 0xe6, 0x4a, 0x8a, 0xe6, 0x8d, 0x88, 0xc1, 0x95, 0x15, - 0x1e, 0xbf, 0x1f, 0x67, 0xa1, 0xb8, 0x99, 0x6c, 0xcd, 0xe1, 0x65, 0xae, 0x26, 0x20, 0xf4, 0x46, - 0xc4, 0x40, 0x06, 0xdc, 0x34, 0xe5, 0xa0, 0x67, 0xd4, 0x27, 0xb9, 0xfa, 0xd8, 0x65, 0xbc, 0x64, - 0x9a, 0x5b, 0x73, 0x78, 0x25, 0x50, 0x16, 0xed, 0xa4, 0x06, 0x90, 0x0b, 0x74, 0xab, 0xdf, 0x4b, - 0x40, 0x3e, 0x60, 0x7a, 0xe8, 0xcb, 0x90, 0x77, 0x74, 0x87, 0x6a, 0xc6, 0x68, 0xec, 0xf1, 0x09, - 0xa5, 0x6a, 0xc5, 0xf3, 0xb3, 0xf5, 0x5c, 0x5b, 0x77, 0x68, 0xbd, 0xbb, 0xeb, 0xe1, 0x1c, 0x63, - 0xd7, 0x47, 0x63, 0x0f, 0xbd, 0x0d, 0xc5, 0x21, 0x19, 0x52, 0x77, 0xa2, 0xed, 0x4d, 0x7c, 0xe2, - 0x49, 0xb3, 0x15, 0x04, 0xad, 0xc6, 0x48, 0xe8, 0x1b, 0x30, 0x3f, 0x10, 0x43, 0x5a, 0x4d, 0xf1, - 0xed, 0xf3, 0x4e, 0xdc, 0xe8, 0x67, 0x46, 0x8d, 0x03, 0x19, 0xf5, 0xb7, 0x13, 0xb0, 0x12, 0x52, - 0xc9, 0x2f, 0x8d, 0x2d, 0x97, 0x0c, 0x89, 0xe3, 0x7b, 0xe8, 0x6b, 0x90, 0xb5, 0xad, 0xa1, 0xe5, - 0x7b, 0xd2, 0xe6, 0x77, 0xe2, 0xd4, 0x86, 0x93, 0xc2, 0x12, 0x8c, 0xaa, 0x50, 0x74, 0x89, 0x47, - 0xdc, 0x23, 0xb1, 0xe3, 0xa5, 0x45, 0xaf, 0x11, 0xbe, 0x20, 0xa2, 0x6e, 0x40, 0xae, 0x6b, 0xeb, - 0xfe, 0x3e, 0x75, 0x87, 0x48, 0x85, 0xa2, 0xee, 0x1a, 0x07, 0x96, 0x4f, 0x0c, 0x7f, 0xec, 0x06, - 0xa7, 0xef, 0x02, 0x0d, 0xdd, 0x84, 0x24, 0x15, 0x1d, 0xe5, 0x6b, 0xd9, 0xf3, 0xb3, 0xf5, 0x64, - 0xa7, 0x87, 0x93, 0xd4, 0x53, 0x3f, 0x82, 0xe5, 0xae, 0x3d, 0x1e, 0x58, 0x4e, 0x83, 0x78, 0x86, - 0x6b, 0x8d, 0x98, 0x76, 0xb6, 0x2b, 0x99, 0x8f, 0x0a, 0x76, 0x25, 0xfb, 0x1d, 0x1e, 0xed, 0xe4, - 0xf4, 0x68, 0xab, 0xbf, 0x99, 0x84, 0xe5, 0xa6, 0x33, 0xb0, 0x1c, 0x12, 0x95, 0xbe, 0x0b, 0x8b, - 0x84, 0x13, 0xb5, 0x23, 0xe1, 0x6e, 0xa4, 0x9e, 0x05, 0x41, 0x0d, 0x7c, 0x50, 0x6b, 0xc6, 0x2f, - 0x3c, 0x8a, 0x9b, 0xfe, 0x6b, 0xda, 0x63, 0xbd, 0x43, 0x13, 0xe6, 0x47, 0x7c, 0x12, 0x9e, 0x5c, - 0xde, 0xbb, 0x71, 0xba, 0x5e, 0x9b, 0x67, 0xe0, 0x24, 0xa4, 0xec, 0x4f, 0xe2, 0x24, 0xfe, 0x24, - 0x09, 0x4b, 0x6d, 0x6a, 0x5e, 0xb0, 0x43, 0x09, 0x72, 0x07, 0xd4, 0xf3, 0x23, 0x0e, 0x31, 0x6c, - 0xa3, 0xa7, 0x90, 0x1b, 0xc9, 0xe5, 0x93, 0xab, 0x7f, 0x3b, 0x7e, 0xc8, 0x02, 0x83, 0x43, 0x34, - 0xfa, 0x08, 0xf2, 0xc1, 0x91, 0x61, 0xb3, 0x7d, 0x83, 0x8d, 0x33, 0xc5, 0xa3, 0x6f, 0x40, 0x56, - 0x2c, 0xc2, 0x6a, 0x9a, 0x4b, 0xde, 0x7d, 0x23, 0x9b, 0x63, 0x29, 0x84, 0x36, 0x21, 0xe7, 0xdb, - 0x9e, 0x66, 0x39, 0xfb, 0x74, 0x35, 0xc3, 0x15, 0xac, 0xc7, 0x3a, 0x19, 0x6a, 0x92, 0xfe, 0x76, - 0xaf, 0xe5, 0xec, 0xd3, 0x5a, 0xe1, 0xfc, 0x6c, 0x7d, 0x5e, 0x36, 0xf0, 0xbc, 0x6f, 0x7b, 0xec, - 0x87, 0xfa, 0x3b, 0x09, 0x28, 0x44, 0x50, 0xe8, 0x0e, 0x80, 0xef, 0x8e, 0x3d, 0x5f, 0x73, 0x29, - 0xf5, 0xb9, 0xb1, 0x8a, 0x38, 0xcf, 0x29, 0x98, 0x52, 0x1f, 0x55, 0xe0, 0x86, 0x41, 0x5c, 0x5f, - 0xb3, 0x3c, 0x6f, 0x4c, 0x5c, 0xcd, 0x1b, 0xef, 0x7d, 0x4a, 0x0c, 0x9f, 0x1b, 0xae, 0x88, 0x97, - 0x19, 0xab, 0xc5, 0x39, 0x3d, 0xc1, 0x40, 0x4f, 0xe0, 0x66, 0x14, 0x3f, 0x1a, 0xef, 0xd9, 0x96, - 0xa1, 0xb1, 0xc5, 0x4c, 0x71, 0x91, 0x1b, 0x53, 0x91, 0x2e, 0xe7, 0x3d, 0x23, 0x13, 0xf5, 0x87, - 0x09, 0x50, 0xb0, 0xbe, 0xef, 0xef, 0x90, 0xe1, 0x1e, 0x71, 0x7b, 0xbe, 0xee, 0x8f, 0x3d, 0x74, - 0x13, 0xb2, 0x36, 0xd1, 0x4d, 0xe2, 0xf2, 0x41, 0xe5, 0xb0, 0x6c, 0xa1, 0x5d, 0x76, 0x82, 0x75, - 0xe3, 0x40, 0xdf, 0xb3, 0x6c, 0xcb, 0x9f, 0xf0, 0xa1, 0x2c, 0xc6, 0x6f, 0xe1, 0x59, 0x9d, 0x15, - 0x1c, 0x11, 0xc4, 0x17, 0xd4, 0xa0, 0x55, 0x98, 0x1f, 0x12, 0xcf, 0xd3, 0x07, 0x84, 0x8f, 0x34, - 0x8f, 0x83, 0xa6, 0xfa, 0x11, 0x14, 0xa3, 0x72, 0xa8, 0x00, 0xf3, 0xbb, 0xed, 0x67, 0xed, 0xce, - 0x8b, 0xb6, 0x32, 0x87, 0x96, 0xa0, 0xb0, 0xdb, 0xc6, 0xcd, 0x6a, 0x7d, 0xab, 0x5a, 0xdb, 0x6e, - 0x2a, 0x09, 0xb4, 0x00, 0xf9, 0x69, 0x33, 0xa9, 0xfe, 0x59, 0x02, 0x80, 0x99, 0x5b, 0x4e, 0xea, - 0x43, 0xc8, 0x78, 0xbe, 0xee, 0x8b, 0x5d, 0xb9, 0xf8, 0xf8, 0xdd, 0xcb, 0xd6, 0x50, 0x8e, 0x97, - 0xfd, 0x23, 0x58, 0x88, 0x44, 0x47, 0x98, 0xbc, 0x30, 0x42, 0xe6, 0x20, 0x74, 0xd3, 0x74, 0xe5, - 0xc0, 0xf9, 0x6f, 0xf5, 0x23, 0xc8, 0x70, 0xe9, 0x8b, 0xc3, 0xcd, 0x41, 0xba, 0xc1, 0x7e, 0x25, - 0x50, 0x1e, 0x32, 0xb8, 0x59, 0x6d, 0x7c, 0x47, 0x49, 0x22, 0x05, 0x8a, 0x8d, 0x56, 0xaf, 0xde, - 0x69, 0xb7, 0x9b, 0xf5, 0x7e, 0xb3, 0xa1, 0xa4, 0xd4, 0xbb, 0x90, 0x69, 0x0d, 0x99, 0xe6, 0xdb, - 0x6c, 0xcb, 0xef, 0x13, 0x97, 0x38, 0x46, 0x70, 0x92, 0xa6, 0x04, 0xf5, 0xc7, 0x05, 0xc8, 0xec, - 0xd0, 0xb1, 0xe3, 0xa3, 0xc7, 0x11, 0xb7, 0xb5, 0x18, 0x1f, 0x21, 0x70, 0x60, 0xa5, 0x3f, 0x19, - 0x11, 0xe9, 0xd6, 0x6e, 0x42, 0x56, 0x1c, 0x0e, 0x39, 0x1d, 0xd9, 0x62, 0x74, 0x5f, 0x77, 0x07, - 0xc4, 0x97, 0xf3, 0x91, 0x2d, 0xf4, 0x3e, 0xbb, 0xb1, 0x74, 0x93, 0x3a, 0xf6, 0x84, 0x9f, 0xa1, - 0x9c, 0xb8, 0x96, 0x30, 0xd1, 0xcd, 0x8e, 0x63, 0x4f, 0x70, 0xc8, 0x45, 0x5b, 0x50, 0xdc, 0xb3, - 0x1c, 0x53, 0xa3, 0x23, 0xe1, 0xe4, 0x33, 0x97, 0x9f, 0x38, 0x31, 0xaa, 0x9a, 0xe5, 0x98, 0x1d, - 0x01, 0xc6, 0x85, 0xbd, 0x69, 0x03, 0xb5, 0x61, 0xf1, 0x88, 0xda, 0xe3, 0x21, 0x09, 0x75, 0x65, - 0xb9, 0xae, 0xf7, 0x2e, 0xd7, 0xf5, 0x9c, 0xe3, 0x03, 0x6d, 0x0b, 0x47, 0xd1, 0x26, 0x7a, 0x06, - 0x0b, 0xfe, 0x70, 0xb4, 0xef, 0x85, 0xea, 0xe6, 0xb9, 0xba, 0x2f, 0x5d, 0x61, 0x30, 0x06, 0x0f, - 0xb4, 0x15, 0xfd, 0x48, 0x0b, 0x6d, 0x42, 0xc1, 0xa0, 0x8e, 0x67, 0x79, 0x3e, 0x71, 0x8c, 0xc9, - 0x6a, 0x8e, 0xdb, 0xfe, 0x8a, 0x59, 0xd6, 0xa7, 0x60, 0x1c, 0x95, 0x2c, 0xfd, 0x7a, 0x0a, 0x0a, - 0x11, 0x13, 0xa0, 0x1e, 0x14, 0x46, 0x2e, 0x1d, 0xe9, 0x03, 0x7e, 0xe3, 0xc9, 0x45, 0x7d, 0xf4, - 0x46, 0xe6, 0xab, 0x74, 0xa7, 0x82, 0x38, 0xaa, 0x45, 0x3d, 0x4d, 0x42, 0x21, 0xc2, 0x44, 0xf7, - 0x20, 0x87, 0xbb, 0xb8, 0xf5, 0xbc, 0xda, 0x6f, 0x2a, 0x73, 0xa5, 0xdb, 0x27, 0xa7, 0xe5, 0x55, - 0xae, 0x2d, 0xaa, 0xa0, 0xeb, 0x5a, 0x47, 0x6c, 0x0f, 0xbf, 0x0f, 0xf3, 0x01, 0x34, 0x51, 0xfa, - 0xe2, 0xc9, 0x69, 0xf9, 0xad, 0x59, 0x68, 0x04, 0x89, 0x7b, 0x5b, 0x55, 0xdc, 0x6c, 0x28, 0xc9, - 0x78, 0x24, 0xee, 0x1d, 0xe8, 0x2e, 0x31, 0xd1, 0x97, 0x20, 0x2b, 0x81, 0xa9, 0x52, 0xe9, 0xe4, - 0xb4, 0x7c, 0x73, 0x16, 0x38, 0xc5, 0xe1, 0xde, 0x76, 0xf5, 0x79, 0x53, 0x49, 0xc7, 0xe3, 0x70, - 0xcf, 0xd6, 0x8f, 0x08, 0x7a, 0x17, 0x32, 0x02, 0x96, 0x29, 0xdd, 0x3a, 0x39, 0x2d, 0x7f, 0xe1, - 0x35, 0x75, 0x0c, 0x55, 0x5a, 0xfd, 0xad, 0x3f, 0x5a, 0x9b, 0xfb, 0xab, 0x3f, 0x5e, 0x53, 0x66, - 0xd9, 0xa5, 0xff, 0x49, 0xc0, 0xc2, 0x85, 0xbd, 0x83, 0x54, 0xc8, 0x3a, 0xd4, 0xa0, 0x23, 0x71, - 0x11, 0xe6, 0x6a, 0x70, 0x7e, 0xb6, 0x9e, 0x6d, 0xd3, 0x3a, 0x1d, 0x4d, 0xb0, 0xe4, 0xa0, 0x67, - 0x33, 0x57, 0xf9, 0x93, 0x37, 0xdc, 0x98, 0xb1, 0x97, 0xf9, 0xc7, 0xb0, 0x60, 0xba, 0xd6, 0x11, - 0x71, 0x35, 0x83, 0x3a, 0xfb, 0xd6, 0x40, 0x5e, 0x72, 0xa5, 0xd8, 0x78, 0x93, 0x03, 0x71, 0x51, - 0x08, 0xd4, 0x39, 0xfe, 0x27, 0xb8, 0xc6, 0x4b, 0xcf, 0xa1, 0x18, 0xdd, 0xea, 0xec, 0x5e, 0xf2, - 0xac, 0x5f, 0x26, 0x32, 0xb0, 0xe4, 0x61, 0x28, 0xce, 0x33, 0x8a, 0x08, 0x2b, 0xdf, 0x83, 0xf4, - 0x90, 0x9a, 0x42, 0xcf, 0x42, 0xed, 0x06, 0x8b, 0x26, 0xfe, 0xf9, 0x6c, 0xbd, 0x40, 0xbd, 0xca, - 0x86, 0x65, 0x93, 0x1d, 0x6a, 0x12, 0xcc, 0x01, 0xea, 0x11, 0xa4, 0x99, 0xcf, 0x41, 0x5f, 0x84, - 0x74, 0xad, 0xd5, 0x6e, 0x28, 0x73, 0xa5, 0xe5, 0x93, 0xd3, 0xf2, 0x02, 0x37, 0x09, 0x63, 0xb0, - 0xbd, 0x8b, 0xd6, 0x21, 0xfb, 0xbc, 0xb3, 0xbd, 0xbb, 0xc3, 0xb6, 0xd7, 0x8d, 0x93, 0xd3, 0xf2, - 0x52, 0xc8, 0x16, 0x46, 0x43, 0x77, 0x20, 0xd3, 0xdf, 0xe9, 0x6e, 0xf4, 0x94, 0x64, 0x09, 0x9d, - 0x9c, 0x96, 0x17, 0x43, 0x3e, 0x1f, 0x73, 0x69, 0x59, 0xae, 0x6a, 0x3e, 0xa4, 0xab, 0x3f, 0x48, - 0x40, 0x21, 0x72, 0xe0, 0xd8, 0xc6, 0x6c, 0x34, 0x37, 0xaa, 0xbb, 0xdb, 0x7d, 0x65, 0x2e, 0xb2, - 0x31, 0x23, 0x90, 0x06, 0xd9, 0xd7, 0xc7, 0x36, 0xf3, 0x73, 0x50, 0xef, 0xb4, 0x7b, 0xad, 0x5e, - 0xbf, 0xd9, 0xee, 0x2b, 0x89, 0xd2, 0xea, 0xc9, 0x69, 0x79, 0x65, 0x16, 0xbc, 0x31, 0xb6, 0x6d, - 0xb6, 0x35, 0xeb, 0xd5, 0xfa, 0x16, 0xdf, 0xeb, 0xd3, 0xad, 0x19, 0x41, 0xd5, 0x75, 0xe3, 0x80, - 0x98, 0xe8, 0x3e, 0xe4, 0x1b, 0xcd, 0xed, 0xe6, 0x66, 0x95, 0x7b, 0xf7, 0xd2, 0x9d, 0x93, 0xd3, - 0xf2, 0xad, 0xd7, 0x7b, 0xb7, 0xc9, 0x40, 0xf7, 0x89, 0x39, 0xb3, 0x45, 0x23, 0x10, 0xf5, 0xbf, - 0x92, 0xb0, 0x80, 0x59, 0x3a, 0xec, 0xfa, 0x5d, 0x6a, 0x5b, 0xc6, 0x04, 0x75, 0x21, 0x6f, 0x50, - 0xc7, 0xb4, 0x22, 0x7e, 0xe2, 0xf1, 0x25, 0x21, 0xd1, 0x54, 0x2a, 0x68, 0xd5, 0x03, 0x49, 0x3c, - 0x55, 0x82, 0x1e, 0x40, 0xc6, 0x24, 0xb6, 0x3e, 0x91, 0xb1, 0xd9, 0xad, 0x8a, 0x48, 0xb8, 0x2b, - 0x41, 0xc2, 0x5d, 0x69, 0xc8, 0x84, 0x1b, 0x0b, 0x1c, 0xcf, 0x41, 0xf4, 0x97, 0x9a, 0xee, 0xfb, - 0x64, 0x38, 0xf2, 0x45, 0x60, 0x96, 0xc6, 0x85, 0xa1, 0xfe, 0xb2, 0x2a, 0x49, 0xe8, 0x11, 0x64, - 0x8f, 0x2d, 0xc7, 0xa4, 0xc7, 0x32, 0xf6, 0xba, 0x42, 0xa9, 0x04, 0xaa, 0x27, 0x2c, 0x24, 0x99, - 0x19, 0x26, 0xdb, 0x43, 0xed, 0x4e, 0xbb, 0x19, 0xec, 0x21, 0xc9, 0xef, 0x38, 0x6d, 0xea, 0xb0, - 0xf3, 0x0f, 0x9d, 0xb6, 0xb6, 0x51, 0x6d, 0x6d, 0xef, 0x62, 0xb6, 0x8f, 0x56, 0x4e, 0x4e, 0xcb, - 0x4a, 0x08, 0xd9, 0xd0, 0x2d, 0x9b, 0x25, 0x03, 0xb7, 0x20, 0x55, 0x6d, 0x7f, 0x47, 0x49, 0x96, - 0x94, 0x93, 0xd3, 0x72, 0x31, 0x64, 0x57, 0x9d, 0xc9, 0xd4, 0xee, 0xb3, 0xfd, 0xaa, 0x7f, 0x97, - 0x82, 0xe2, 0xee, 0xc8, 0xd4, 0x7d, 0x22, 0xce, 0x19, 0x2a, 0x43, 0x61, 0xa4, 0xbb, 0xba, 0x6d, - 0x13, 0xdb, 0xf2, 0x86, 0xb2, 0x94, 0x10, 0x25, 0xa1, 0x0f, 0xde, 0xd4, 0x8c, 0xb5, 0x1c, 0x3b, - 0x3b, 0xdf, 0xfb, 0xd7, 0xf5, 0x44, 0x60, 0xd0, 0x5d, 0x58, 0xdc, 0x17, 0xa3, 0xd5, 0x74, 0x83, - 0x2f, 0x6c, 0x8a, 0x2f, 0x6c, 0x25, 0x6e, 0x61, 0xa3, 0xc3, 0xaa, 0xc8, 0x49, 0x56, 0xb9, 0x14, - 0x5e, 0xd8, 0x8f, 0x36, 0xd1, 0x13, 0x98, 0x1f, 0x52, 0xc7, 0xf2, 0xa9, 0x7b, 0xfd, 0x2a, 0x04, - 0x48, 0x74, 0x0f, 0x96, 0xd9, 0xe2, 0x06, 0xe3, 0xe1, 0x6c, 0x7e, 0x9d, 0x27, 0xf1, 0xd2, 0x50, - 0x7f, 0x29, 0x3b, 0xc4, 0x8c, 0x8c, 0x6a, 0x90, 0xa1, 0x2e, 0x8b, 0x17, 0xb3, 0x7c, 0xb8, 0xf7, - 0xaf, 0x1d, 0xae, 0x68, 0x74, 0x98, 0x0c, 0x16, 0xa2, 0xea, 0xd7, 0x61, 0xe1, 0xc2, 0x24, 0x58, - 0x98, 0xd4, 0xad, 0xee, 0xf6, 0x9a, 0xca, 0x1c, 0x2a, 0x42, 0xae, 0xde, 0x69, 0xf7, 0x5b, 0xed, - 0x5d, 0x16, 0xe7, 0x15, 0x21, 0x87, 0x3b, 0xdb, 0xdb, 0xb5, 0x6a, 0xfd, 0x99, 0x92, 0x54, 0x2b, - 0x50, 0x88, 0x68, 0x43, 0x8b, 0x00, 0xbd, 0x7e, 0xa7, 0xab, 0x6d, 0xb4, 0x70, 0xaf, 0x2f, 0xa2, - 0xc4, 0x5e, 0xbf, 0x8a, 0xfb, 0x92, 0x90, 0x50, 0xff, 0x23, 0x19, 0xac, 0xa8, 0x0c, 0x0c, 0x6b, - 0x17, 0x03, 0xc3, 0x2b, 0x06, 0x2f, 0x43, 0xc3, 0x69, 0x23, 0x0c, 0x10, 0x3f, 0x00, 0xe0, 0x1b, - 0x87, 0x98, 0x9a, 0xee, 0xcb, 0x85, 0x2f, 0xbd, 0x66, 0xe4, 0x7e, 0x50, 0xd1, 0xc2, 0x79, 0x89, - 0xae, 0xfa, 0xe8, 0x1b, 0x50, 0x34, 0xe8, 0x70, 0x64, 0x13, 0x29, 0x9c, 0xba, 0x56, 0xb8, 0x10, - 0xe2, 0xab, 0x7e, 0x34, 0x34, 0x4d, 0x5f, 0x0c, 0x9e, 0x7f, 0x23, 0x11, 0x58, 0x26, 0x26, 0x1a, - 0x2d, 0x42, 0x6e, 0xb7, 0xdb, 0xa8, 0xf6, 0x5b, 0xed, 0x4d, 0x25, 0x81, 0x00, 0xb2, 0xdc, 0xd4, - 0x0d, 0x25, 0xc9, 0xa2, 0xe8, 0x7a, 0x67, 0xa7, 0xbb, 0xdd, 0xe4, 0x1e, 0x0b, 0xad, 0x80, 0x12, - 0x18, 0x5b, 0xe3, 0x86, 0x6c, 0x36, 0x94, 0x34, 0xba, 0x01, 0x4b, 0x21, 0x55, 0x4a, 0x66, 0xd0, - 0x4d, 0x40, 0x21, 0x71, 0xaa, 0x22, 0xab, 0xfe, 0x2a, 0x2c, 0xd5, 0xa9, 0xe3, 0xeb, 0x96, 0x13, - 0x66, 0x18, 0x8f, 0xd9, 0xa4, 0x25, 0x49, 0xb3, 0x64, 0x25, 0xa8, 0xb6, 0x74, 0x7e, 0xb6, 0x5e, - 0x08, 0xa1, 0xad, 0x06, 0x0f, 0x95, 0x64, 0xc3, 0x64, 0xe7, 0x77, 0x64, 0x99, 0xdc, 0xb8, 0x99, - 0xda, 0xfc, 0xf9, 0xd9, 0x7a, 0xaa, 0xdb, 0x6a, 0x60, 0x46, 0x43, 0x5f, 0x84, 0x3c, 0x79, 0x69, - 0xf9, 0x9a, 0xc1, 0xee, 0x25, 0x66, 0xc0, 0x0c, 0xce, 0x31, 0x42, 0x9d, 0x5d, 0x43, 0x35, 0x80, - 0x2e, 0x75, 0x7d, 0xd9, 0xf3, 0x57, 0x21, 0x33, 0xa2, 0x2e, 0xaf, 0x5d, 0x5c, 0x5a, 0x51, 0x63, - 0x70, 0xb1, 0x51, 0xb1, 0x00, 0xab, 0xbf, 0x97, 0x02, 0xe8, 0xeb, 0xde, 0xa1, 0x54, 0xf2, 0x14, - 0xf2, 0x61, 0x75, 0x52, 0x16, 0x41, 0xae, 0x5c, 0xed, 0x10, 0x8c, 0x9e, 0x04, 0x9b, 0x4d, 0xe4, - 0x4e, 0xb1, 0x49, 0x6c, 0xd0, 0x51, 0x5c, 0xfa, 0x71, 0x31, 0x41, 0x62, 0xd7, 0x3c, 0x71, 0x5d, - 0xb9, 0xf2, 0xec, 0x27, 0xaa, 0xf3, 0x6b, 0x41, 0x18, 0x4d, 0x46, 0xdf, 0xb1, 0x65, 0x9f, 0x99, - 0x15, 0xd9, 0x9a, 0xc3, 0x53, 0x39, 0xf4, 0x31, 0x14, 0xd8, 0xbc, 0x35, 0x8f, 0xf3, 0x64, 0xe0, - 0x7d, 0xa9, 0xa9, 0x84, 0x06, 0x0c, 0xa3, 0xa9, 0x95, 0xef, 0x00, 0xe8, 0xa3, 0x91, 0x6d, 0x11, - 0x53, 0xdb, 0x9b, 0xf0, 0x48, 0x3b, 0x8f, 0xf3, 0x92, 0x52, 0x9b, 0xb0, 0xe3, 0x12, 0xb0, 0x75, - 0x9f, 0x47, 0xcf, 0xd7, 0x18, 0x50, 0xa2, 0xab, 0x7e, 0x4d, 0x81, 0x45, 0x77, 0xec, 0x30, 0x83, - 0xca, 0xd1, 0xa9, 0x7f, 0x9a, 0x84, 0xb7, 0xda, 0xc4, 0x3f, 0xa6, 0xee, 0x61, 0xd5, 0xf7, 0x75, - 0xe3, 0x60, 0x48, 0x1c, 0xb9, 0x7c, 0x91, 0x84, 0x26, 0x71, 0x21, 0xa1, 0x59, 0x85, 0x79, 0xdd, - 0xb6, 0x74, 0x8f, 0x88, 0xe0, 0x2d, 0x8f, 0x83, 0x26, 0x4b, 0xbb, 0x58, 0x12, 0x47, 0x3c, 0x8f, - 0x88, 0xba, 0x0a, 0x1b, 0x78, 0x40, 0x40, 0xdf, 0x85, 0x9b, 0x32, 0x4c, 0xd3, 0xc3, 0xae, 0x58, - 0x42, 0x11, 0x14, 0x68, 0x9b, 0xb1, 0x59, 0x65, 0xfc, 0xe0, 0x64, 0x1c, 0x37, 0x25, 0x77, 0x46, - 0xbe, 0x8c, 0x0a, 0x57, 0xcc, 0x18, 0x56, 0x69, 0x13, 0x6e, 0x5d, 0x2a, 0xf2, 0xb9, 0xea, 0x36, - 0xff, 0x98, 0x04, 0x68, 0x75, 0xab, 0x3b, 0xd2, 0x48, 0x0d, 0xc8, 0xee, 0xeb, 0x43, 0xcb, 0x9e, - 0x5c, 0xe5, 0x01, 0xa7, 0xf8, 0x4a, 0x55, 0x98, 0x63, 0x83, 0xcb, 0x60, 0x29, 0xcb, 0x73, 0xca, - 0xf1, 0x9e, 0x43, 0xfc, 0x30, 0xa7, 0xe4, 0x2d, 0x36, 0x0c, 0x57, 0x77, 0xc2, 0xad, 0x2b, 0x1a, - 0x6c, 0x01, 0x58, 0xc8, 0x73, 0xac, 0x4f, 0x02, 0xb7, 0x25, 0x9b, 0x68, 0x8b, 0x57, 0x47, 0x89, - 0x7b, 0x44, 0xcc, 0xd5, 0x0c, 0x37, 0xea, 0x75, 0xe3, 0xc1, 0x12, 0x2e, 0x6c, 0x17, 0x4a, 0x97, - 0x3e, 0xe2, 0x21, 0xd3, 0x94, 0xf5, 0xb9, 0x6c, 0xf4, 0x10, 0x16, 0x2e, 0xcc, 0xf3, 0xb5, 0x64, - 0xbe, 0xd5, 0x7d, 0xfe, 0x55, 0x25, 0x2d, 0x7f, 0x7d, 0x5d, 0xc9, 0xaa, 0x7f, 0x9b, 0x12, 0x8e, - 0x46, 0x5a, 0x35, 0xfe, 0x55, 0x20, 0xc7, 0x77, 0xb7, 0x41, 0x6d, 0xe9, 0x00, 0xde, 0xbb, 0xda, - 0xff, 0xb0, 0x9c, 0x8e, 0xc3, 0x71, 0x28, 0x88, 0xd6, 0xa1, 0x20, 0x76, 0xb1, 0xc6, 0x0e, 0x1c, - 0x37, 0xeb, 0x02, 0x06, 0x41, 0x62, 0x92, 0xe8, 0x2e, 0x2c, 0xf2, 0xe2, 0x8f, 0x77, 0x40, 0x4c, - 0x81, 0x49, 0x73, 0xcc, 0x42, 0x48, 0xe5, 0xb0, 0x1d, 0x28, 0x4a, 0x82, 0xc6, 0xe3, 0xf9, 0x0c, - 0x1f, 0xd0, 0xbd, 0xeb, 0x06, 0x24, 0x44, 0x78, 0x98, 0x5f, 0x18, 0x4d, 0x1b, 0xea, 0x2f, 0x42, - 0x2e, 0x18, 0x2c, 0x5a, 0x85, 0x54, 0xbf, 0xde, 0x55, 0xe6, 0x4a, 0x4b, 0x27, 0xa7, 0xe5, 0x42, - 0x40, 0xee, 0xd7, 0xbb, 0x8c, 0xb3, 0xdb, 0xe8, 0x2a, 0x89, 0x8b, 0x9c, 0xdd, 0x46, 0x17, 0x95, - 0x20, 0xdd, 0xab, 0xf7, 0xbb, 0x41, 0x7c, 0x16, 0xb0, 0x18, 0xad, 0x94, 0x66, 0xf1, 0x99, 0xba, - 0x0f, 0x85, 0x48, 0xef, 0xe8, 0x1d, 0x98, 0x6f, 0xb5, 0x37, 0x71, 0xb3, 0xd7, 0x53, 0xe6, 0x4a, - 0x37, 0x4f, 0x4e, 0xcb, 0x28, 0xc2, 0x6d, 0x39, 0x03, 0xb6, 0x76, 0xe8, 0x0e, 0xa4, 0xb7, 0x3a, - 0xec, 0xde, 0x17, 0xc9, 0x45, 0x04, 0xb1, 0x45, 0x3d, 0xbf, 0x74, 0x43, 0x06, 0x7e, 0x51, 0xc5, - 0xea, 0xef, 0x27, 0x20, 0x2b, 0x0e, 0x5a, 0xec, 0x22, 0x56, 0x61, 0x3e, 0x28, 0x21, 0x88, 0xc4, - 0xef, 0xbd, 0xcb, 0x93, 0xb4, 0x8a, 0xcc, 0xa9, 0xc4, 0xd6, 0x0c, 0xe4, 0x4a, 0x1f, 0x42, 0x31, - 0xca, 0xf8, 0x5c, 0x1b, 0xf3, 0xbb, 0x50, 0x60, 0x7b, 0x3f, 0x48, 0xd6, 0x1e, 0x43, 0x56, 0x38, - 0x8b, 0xf0, 0x1e, 0xba, 0x3c, 0x63, 0x94, 0x48, 0xf4, 0x14, 0xe6, 0x45, 0x96, 0x19, 0x54, 0x8e, - 0xd7, 0xae, 0x3e, 0x61, 0x38, 0x80, 0xab, 0x1f, 0x43, 0xba, 0x4b, 0x88, 0xcb, 0x6c, 0xef, 0x50, - 0x93, 0x4c, 0xaf, 0x6e, 0x99, 0x20, 0x9b, 0xa4, 0xd5, 0x60, 0x09, 0xb2, 0x49, 0x5a, 0x66, 0x58, - 0x1b, 0x4b, 0x46, 0x6a, 0x63, 0x7d, 0x28, 0xbe, 0x20, 0xd6, 0xe0, 0xc0, 0x27, 0x26, 0x57, 0x74, - 0x1f, 0xd2, 0x23, 0x12, 0x0e, 0x7e, 0x35, 0x76, 0xf3, 0x11, 0xe2, 0x62, 0x8e, 0x62, 0x3e, 0xe6, - 0x98, 0x4b, 0xcb, 0xe7, 0x0e, 0xd9, 0x52, 0xff, 0x21, 0x09, 0x8b, 0x2d, 0xcf, 0x1b, 0xeb, 0x8e, - 0x11, 0x44, 0x75, 0xdf, 0xbc, 0x18, 0xd5, 0xc5, 0xbe, 0x0b, 0x5d, 0x14, 0xb9, 0x58, 0xf2, 0x93, - 0x37, 0x6b, 0x32, 0xbc, 0x59, 0xd5, 0x1f, 0x27, 0x82, 0xba, 0xde, 0xdd, 0x88, 0x2b, 0x10, 0x39, - 0x62, 0x54, 0x13, 0xd9, 0x75, 0x0e, 0x1d, 0x7a, 0xec, 0xa0, 0xb7, 0x21, 0x83, 0x9b, 0xed, 0xe6, - 0x0b, 0x25, 0x21, 0xb6, 0xe7, 0x05, 0x10, 0x26, 0x0e, 0x39, 0x66, 0x9a, 0xba, 0xcd, 0x76, 0x83, - 0x45, 0x61, 0xc9, 0x18, 0x4d, 0x5d, 0xe2, 0x98, 0x96, 0x33, 0x40, 0xef, 0x40, 0xb6, 0xd5, 0xeb, - 0xed, 0xf2, 0x14, 0xf2, 0xad, 0x93, 0xd3, 0xf2, 0x8d, 0x0b, 0x28, 0x5e, 0xd3, 0x35, 0x19, 0x88, - 0xa5, 0x40, 0x2c, 0x3e, 0x8b, 0x01, 0xb1, 0xd8, 0x5a, 0x80, 0x70, 0xa7, 0x5f, 0xed, 0x37, 0x95, - 0x4c, 0x0c, 0x08, 0x53, 0xf6, 0x57, 0x1e, 0xb7, 0x7f, 0x49, 0x82, 0x52, 0x35, 0x0c, 0x32, 0xf2, - 0x19, 0x5f, 0x66, 0x9d, 0x7d, 0xc8, 0x8d, 0xd8, 0x2f, 0x8b, 0x04, 0x11, 0xd4, 0xd3, 0xd8, 0x97, - 0xcd, 0x19, 0xb9, 0x0a, 0xa6, 0x36, 0xa9, 0x9a, 0x43, 0xcb, 0xf3, 0x2c, 0xea, 0x08, 0x1a, 0x0e, - 0x35, 0x95, 0xfe, 0x33, 0x01, 0x37, 0x62, 0x10, 0xe8, 0x21, 0xa4, 0x5d, 0x6a, 0x07, 0x6b, 0x78, - 0xfb, 0xb2, 0x92, 0x2d, 0x13, 0xc5, 0x1c, 0x89, 0xd6, 0x00, 0xf4, 0xb1, 0x4f, 0x75, 0xde, 0x3f, - 0x5f, 0xbd, 0x1c, 0x8e, 0x50, 0xd0, 0x0b, 0xc8, 0x7a, 0xc4, 0x70, 0x49, 0x10, 0x67, 0x7f, 0xfc, - 0xff, 0x1d, 0x7d, 0xa5, 0xc7, 0xd5, 0x60, 0xa9, 0xae, 0x54, 0x81, 0xac, 0xa0, 0xb0, 0x6d, 0x6f, - 0xea, 0xbe, 0x2e, 0x0b, 0xfa, 0xfc, 0x37, 0xdb, 0x4d, 0xba, 0x3d, 0x08, 0x76, 0x93, 0x6e, 0x0f, - 0xd4, 0xbf, 0x49, 0x02, 0x34, 0x5f, 0xfa, 0xc4, 0x75, 0x74, 0xbb, 0x5e, 0x45, 0xcd, 0xc8, 0xcd, - 0x20, 0x66, 0xfb, 0xe5, 0xd8, 0x57, 0x8a, 0x50, 0xa2, 0x52, 0xaf, 0xc6, 0xdc, 0x0d, 0xb7, 0x20, - 0x35, 0x76, 0xe5, 0x63, 0xb5, 0x88, 0x91, 0x77, 0xf1, 0x36, 0x66, 0x34, 0xd4, 0x9c, 0xba, 0xad, - 0xd4, 0xe5, 0x4f, 0xd2, 0x91, 0x0e, 0x62, 0x5d, 0x17, 0x3b, 0xf9, 0x86, 0xae, 0x19, 0x44, 0xde, - 0x2a, 0x45, 0x71, 0xf2, 0xeb, 0xd5, 0x3a, 0x71, 0x7d, 0x9c, 0x35, 0x74, 0xf6, 0xff, 0x27, 0xf2, - 0x6f, 0xf7, 0x01, 0xa6, 0x53, 0x43, 0x6b, 0x90, 0xa9, 0x6f, 0xf4, 0x7a, 0xdb, 0xca, 0x9c, 0x70, - 0xe0, 0x53, 0x16, 0x27, 0xab, 0x7f, 0x99, 0x84, 0x5c, 0xbd, 0x2a, 0xaf, 0xdc, 0x3a, 0x28, 0xdc, - 0x2b, 0xf1, 0x67, 0x10, 0xf2, 0x72, 0x64, 0xb9, 0x13, 0xe9, 0x58, 0xae, 0x48, 0x78, 0x17, 0x99, - 0x08, 0x1b, 0x75, 0x93, 0x0b, 0x20, 0x0c, 0x45, 0x22, 0x8d, 0xa0, 0x19, 0x7a, 0xe0, 0xe3, 0xd7, - 0xae, 0x36, 0x96, 0x48, 0x5d, 0xa6, 0x6d, 0x0f, 0x17, 0x02, 0x25, 0x75, 0xdd, 0x43, 0x1f, 0xc0, - 0x92, 0x67, 0x0d, 0x1c, 0xcb, 0x19, 0x68, 0x81, 0xf1, 0xf8, 0x9b, 0x4c, 0x6d, 0xf9, 0xfc, 0x6c, - 0x7d, 0xa1, 0x27, 0x58, 0xd2, 0x86, 0x0b, 0x12, 0x59, 0xe7, 0xa6, 0x44, 0x5f, 0x87, 0xc5, 0x88, - 0x28, 0xb3, 0xa2, 0x30, 0xbb, 0x72, 0x7e, 0xb6, 0x5e, 0x0c, 0x25, 0x9f, 0x91, 0x09, 0x2e, 0x86, - 0x82, 0xcf, 0x08, 0xaf, 0xcd, 0xec, 0x53, 0xd7, 0x20, 0x9a, 0xcb, 0xcf, 0x34, 0xbf, 0xdd, 0xd3, - 0xb8, 0xc0, 0x69, 0xe2, 0x98, 0xab, 0xcf, 0xe1, 0x46, 0xc7, 0x35, 0x0e, 0x88, 0xe7, 0x0b, 0x53, - 0x48, 0x2b, 0x7e, 0x0c, 0xb7, 0x7d, 0xdd, 0x3b, 0xd4, 0x0e, 0x2c, 0xcf, 0xa7, 0xee, 0x44, 0x73, - 0x89, 0x4f, 0x1c, 0xc6, 0xd7, 0xf8, 0x43, 0xae, 0x2c, 0x08, 0xde, 0x62, 0x98, 0x2d, 0x01, 0xc1, - 0x01, 0x62, 0x9b, 0x01, 0xd4, 0x16, 0x14, 0x59, 0x0a, 0x23, 0x8b, 0x6a, 0x6c, 0xf6, 0x60, 0xd3, - 0x81, 0xf6, 0xc6, 0xd7, 0x54, 0xde, 0xa6, 0x03, 0xf1, 0x53, 0xfd, 0x36, 0x28, 0x0d, 0xcb, 0x1b, - 0xe9, 0xbe, 0x71, 0x10, 0x54, 0x3a, 0x51, 0x03, 0x94, 0x03, 0xa2, 0xbb, 0xfe, 0x1e, 0xd1, 0x7d, - 0x6d, 0x44, 0x5c, 0x8b, 0x9a, 0xd7, 0xaf, 0xf2, 0x52, 0x28, 0xd2, 0xe5, 0x12, 0xea, 0x7f, 0x27, - 0x00, 0xb0, 0xbe, 0x1f, 0x44, 0x6b, 0x5f, 0x81, 0x65, 0xcf, 0xd1, 0x47, 0xde, 0x01, 0xf5, 0x35, - 0xcb, 0xf1, 0x89, 0x7b, 0xa4, 0xdb, 0xb2, 0xb8, 0xa3, 0x04, 0x8c, 0x96, 0xa4, 0xa3, 0xfb, 0x80, - 0x0e, 0x09, 0x19, 0x69, 0xd4, 0x36, 0xb5, 0x80, 0x29, 0x9e, 0x99, 0xd3, 0x58, 0x61, 0x9c, 0x8e, - 0x6d, 0xf6, 0x02, 0x3a, 0xaa, 0xc1, 0x1a, 0x9b, 0x3e, 0x71, 0x7c, 0xd7, 0x22, 0x9e, 0xb6, 0x4f, - 0x5d, 0xcd, 0xb3, 0xe9, 0xb1, 0xb6, 0x4f, 0x6d, 0x9b, 0x1e, 0x13, 0x37, 0xa8, 0x9b, 0x95, 0x6c, - 0x3a, 0x68, 0x0a, 0xd0, 0x06, 0x75, 0x7b, 0x36, 0x3d, 0xde, 0x08, 0x10, 0x2c, 0xa4, 0x9b, 0xce, - 0xd9, 0xb7, 0x8c, 0xc3, 0x20, 0xa4, 0x0b, 0xa9, 0x7d, 0xcb, 0x38, 0x44, 0xef, 0xc0, 0x02, 0xb1, - 0x09, 0x2f, 0x9f, 0x08, 0x54, 0x86, 0xa3, 0x8a, 0x01, 0x91, 0x81, 0xd4, 0x4f, 0x40, 0x69, 0x3a, - 0x86, 0x3b, 0x19, 0x45, 0xd6, 0xfc, 0x3e, 0x20, 0xe6, 0x24, 0x35, 0x9b, 0x1a, 0x87, 0xda, 0x50, - 0x77, 0xf4, 0x01, 0x1b, 0x97, 0x78, 0xfd, 0x53, 0x18, 0x67, 0x9b, 0x1a, 0x87, 0x3b, 0x92, 0xae, - 0x7e, 0x00, 0xd0, 0x1b, 0xb9, 0x44, 0x37, 0x3b, 0x2c, 0x9a, 0x60, 0xa6, 0xe3, 0x2d, 0xcd, 0x94, - 0xaf, 0xa7, 0xd4, 0x95, 0x47, 0x5d, 0x11, 0x8c, 0x46, 0x48, 0x57, 0x7f, 0x1e, 0x6e, 0x74, 0x6d, - 0xdd, 0xe0, 0x5f, 0x12, 0x74, 0xc3, 0xe7, 0x2c, 0xf4, 0x14, 0xb2, 0x02, 0x2a, 0x57, 0x32, 0xf6, - 0xb8, 0x4d, 0xfb, 0xdc, 0x9a, 0xc3, 0x12, 0x5f, 0x2b, 0x02, 0x4c, 0xf5, 0xa8, 0x7f, 0x9e, 0x80, - 0x7c, 0xa8, 0x1f, 0x95, 0xc5, 0x2b, 0x8d, 0xef, 0xea, 0x96, 0x23, 0x33, 0xfe, 0x3c, 0x8e, 0x92, - 0x50, 0x0b, 0x0a, 0xa3, 0x50, 0xfa, 0xca, 0x78, 0x2e, 0x66, 0xd4, 0x38, 0x2a, 0x8b, 0x3e, 0x84, - 0x7c, 0xf0, 0x5c, 0x1d, 0x78, 0xd8, 0xab, 0x5f, 0xb7, 0xa7, 0x70, 0xf5, 0x9b, 0x00, 0xdf, 0xa2, - 0x96, 0xd3, 0xa7, 0x87, 0xc4, 0xe1, 0xcf, 0xaf, 0x2c, 0x5f, 0x24, 0x81, 0x15, 0x65, 0x8b, 0x97, - 0x01, 0xc4, 0x12, 0x84, 0xaf, 0x90, 0xa2, 0xa9, 0xfe, 0x75, 0x12, 0xb2, 0x98, 0x52, 0xbf, 0x5e, - 0x45, 0x65, 0xc8, 0x4a, 0x3f, 0xc1, 0xef, 0x9f, 0x5a, 0xfe, 0xfc, 0x6c, 0x3d, 0x23, 0x1c, 0x44, - 0xc6, 0xe0, 0x9e, 0x21, 0xe2, 0xc1, 0x93, 0x97, 0x79, 0x70, 0xf4, 0x10, 0x8a, 0x12, 0xa4, 0x1d, - 0xe8, 0xde, 0x81, 0x48, 0xde, 0x6a, 0x8b, 0xe7, 0x67, 0xeb, 0x20, 0x90, 0x5b, 0xba, 0x77, 0x80, - 0x41, 0xa0, 0xd9, 0x6f, 0xd4, 0x84, 0xc2, 0xa7, 0xd4, 0x72, 0x34, 0x9f, 0x4f, 0x42, 0x16, 0x1a, - 0x63, 0xd7, 0x71, 0x3a, 0x55, 0xf9, 0x2d, 0x02, 0x7c, 0x3a, 0x9d, 0x7c, 0x13, 0x16, 0x5c, 0x4a, - 0x7d, 0xe1, 0xb6, 0x2c, 0xea, 0xc8, 0x1a, 0x46, 0x39, 0xb6, 0xb4, 0x4d, 0xa9, 0x8f, 0x25, 0x0e, - 0x17, 0xdd, 0x48, 0x0b, 0x3d, 0x84, 0x15, 0x5b, 0xf7, 0x7c, 0x8d, 0xfb, 0x3b, 0x73, 0xaa, 0x2d, - 0xcb, 0x8f, 0x1a, 0x62, 0xbc, 0x0d, 0xce, 0x0a, 0x24, 0xd4, 0x7f, 0x4a, 0x40, 0x81, 0x4d, 0xc6, - 0xda, 0xb7, 0x0c, 0x16, 0xe4, 0x7d, 0xfe, 0xd8, 0xe3, 0x16, 0xa4, 0x0c, 0xcf, 0x95, 0x46, 0xe5, - 0x97, 0x6f, 0xbd, 0x87, 0x31, 0xa3, 0xa1, 0x4f, 0x20, 0x2b, 0x6b, 0x29, 0x22, 0xec, 0x50, 0xaf, - 0x0f, 0x47, 0xa5, 0x6d, 0xa4, 0x1c, 0xdf, 0xcb, 0xd3, 0xd1, 0x89, 0x4b, 0x00, 0x47, 0x49, 0xe8, - 0x26, 0x24, 0x0d, 0x61, 0x2e, 0xf9, 0xb1, 0x4b, 0xbd, 0x8d, 0x93, 0x86, 0xa3, 0xfe, 0x20, 0x01, - 0x0b, 0xd3, 0x03, 0xcf, 0x76, 0xc0, 0x6d, 0xc8, 0x7b, 0xe3, 0x3d, 0x6f, 0xe2, 0xf9, 0x64, 0x18, - 0x3c, 0x2d, 0x87, 0x04, 0xd4, 0x82, 0xbc, 0x6e, 0x0f, 0xa8, 0x6b, 0xf9, 0x07, 0x43, 0x99, 0xa5, - 0xc6, 0x87, 0x0a, 0x51, 0x9d, 0x95, 0x6a, 0x20, 0x82, 0xa7, 0xd2, 0xc1, 0xbd, 0x2f, 0xbe, 0x3f, - 0xe0, 0xf7, 0xfe, 0xdb, 0x50, 0xb4, 0xf5, 0x21, 0x2f, 0x2e, 0xf9, 0xd6, 0x50, 0xcc, 0x23, 0x8d, - 0x0b, 0x92, 0xd6, 0xb7, 0x86, 0x44, 0x55, 0x21, 0x1f, 0x2a, 0x43, 0x4b, 0x50, 0xa8, 0x36, 0x7b, - 0xda, 0xa3, 0xc7, 0x4f, 0xb5, 0xcd, 0xfa, 0x8e, 0x32, 0x27, 0x63, 0xd3, 0xbf, 0x48, 0xc0, 0x82, - 0x74, 0x47, 0x32, 0xde, 0x7f, 0x07, 0xe6, 0x5d, 0x7d, 0xdf, 0x0f, 0x32, 0x92, 0xb4, 0xd8, 0xd5, - 0xcc, 0xc3, 0xb3, 0x8c, 0x84, 0xb1, 0xe2, 0x33, 0x92, 0xc8, 0xc7, 0x0e, 0xa9, 0x2b, 0x3f, 0x76, - 0x48, 0xff, 0x54, 0x3e, 0x76, 0x50, 0x7f, 0x0d, 0x60, 0xc3, 0xb2, 0x49, 0x5f, 0xd4, 0xa1, 0xe2, - 0xf2, 0x4b, 0x16, 0xc3, 0xc9, 0x3a, 0x67, 0x10, 0xc3, 0xb5, 0x1a, 0x98, 0xd1, 0x18, 0x6b, 0x60, - 0x99, 0xf2, 0x30, 0x72, 0xd6, 0x26, 0x63, 0x0d, 0x2c, 0x33, 0x7c, 0x95, 0x4b, 0x5f, 0xf7, 0x2a, - 0x77, 0x9a, 0x80, 0x25, 0x19, 0xbb, 0x86, 0xee, 0xf7, 0xcb, 0x90, 0x17, 0x61, 0xec, 0x34, 0xa1, - 0xe3, 0x0f, 0xfc, 0x02, 0xd7, 0x6a, 0xe0, 0x9c, 0x60, 0xb7, 0x4c, 0xb4, 0x0e, 0x05, 0x09, 0x8d, - 0x7c, 0x18, 0x05, 0x82, 0xd4, 0x66, 0xc3, 0xff, 0x2a, 0xa4, 0xf7, 0x2d, 0x9b, 0xc8, 0x8d, 0x1e, - 0xeb, 0x00, 0xa6, 0x06, 0xd8, 0x9a, 0xc3, 0x1c, 0x5d, 0xcb, 0x05, 0x85, 0x3a, 0x3e, 0x3e, 0x99, - 0x76, 0x46, 0xc7, 0x27, 0x32, 0xd0, 0x99, 0xf1, 0x09, 0x1c, 0x1b, 0x9f, 0x60, 0x8b, 0xf1, 0x49, - 0x68, 0x74, 0x7c, 0x82, 0xf4, 0x53, 0x19, 0xdf, 0x36, 0xdc, 0xac, 0xd9, 0xba, 0x71, 0x68, 0x5b, - 0x9e, 0x4f, 0xcc, 0xa8, 0xc7, 0x78, 0x0c, 0xd9, 0x0b, 0x41, 0xe7, 0x55, 0x15, 0x4d, 0x89, 0x54, - 0xff, 0x3d, 0x01, 0xc5, 0x2d, 0xa2, 0xdb, 0xfe, 0xc1, 0xb4, 0x6c, 0xe4, 0x13, 0xcf, 0x97, 0x97, - 0x15, 0xff, 0x8d, 0xbe, 0x06, 0xb9, 0x30, 0x26, 0xb9, 0xf6, 0x6d, 0x2e, 0x84, 0xa2, 0x27, 0x30, - 0xcf, 0xce, 0x18, 0x1d, 0x07, 0xc9, 0xce, 0x55, 0xcf, 0x3e, 0x12, 0xc9, 0x2e, 0x19, 0x97, 0xf0, - 0x20, 0x84, 0x6f, 0xa5, 0x0c, 0x0e, 0x9a, 0xe8, 0x67, 0xa1, 0xc8, 0x5f, 0x2d, 0x82, 0x98, 0x2b, - 0x73, 0x9d, 0xce, 0x82, 0x78, 0x78, 0x14, 0xf1, 0xd6, 0xff, 0x26, 0x60, 0x65, 0x47, 0x9f, 0xec, - 0x11, 0xe9, 0x36, 0x88, 0x89, 0x89, 0x41, 0x5d, 0x13, 0x75, 0xa3, 0xee, 0xe6, 0x8a, 0x77, 0xcc, - 0x38, 0xe1, 0x78, 0xaf, 0x13, 0x24, 0x60, 0xc9, 0x48, 0x02, 0xb6, 0x02, 0x19, 0x87, 0x3a, 0x06, - 0x91, 0xbe, 0x48, 0x34, 0x54, 0x2b, 0xea, 0x6a, 0x4a, 0xe1, 0x13, 0x23, 0x2f, 0x40, 0xb5, 0xa9, - 0x1f, 0xf6, 0x86, 0x3e, 0x81, 0x52, 0xaf, 0x59, 0xc7, 0xcd, 0x7e, 0xad, 0xf3, 0x6d, 0xad, 0x57, - 0xdd, 0xee, 0x55, 0x1f, 0x3f, 0xd4, 0xba, 0x9d, 0xed, 0xef, 0x3c, 0x7a, 0xf2, 0xf0, 0x6b, 0x4a, - 0xa2, 0x54, 0x3e, 0x39, 0x2d, 0xdf, 0x6e, 0x57, 0xeb, 0xdb, 0xe2, 0xc4, 0xec, 0xd1, 0x97, 0x3d, - 0xdd, 0xf6, 0xf4, 0xc7, 0x0f, 0xbb, 0xd4, 0x9e, 0x30, 0x0c, 0xdb, 0xd6, 0xc5, 0xe8, 0x7d, 0x15, - 0xbd, 0x86, 0x13, 0x97, 0x5e, 0xc3, 0xd3, 0xdb, 0x3c, 0x79, 0xc9, 0x6d, 0xbe, 0x01, 0x2b, 0x86, - 0x4b, 0x3d, 0x4f, 0x63, 0xd1, 0x3f, 0x31, 0x67, 0xf2, 0x8b, 0x2f, 0x9c, 0x9f, 0xad, 0x2f, 0xd7, - 0x19, 0xbf, 0xc7, 0xd9, 0x52, 0xfd, 0xb2, 0x11, 0x21, 0xf1, 0x9e, 0xd4, 0x3f, 0x48, 0xb1, 0x40, - 0xca, 0x3a, 0xb2, 0x6c, 0x32, 0x20, 0x1e, 0x7a, 0x0e, 0x4b, 0x86, 0x4b, 0x4c, 0x16, 0xd6, 0xeb, - 0x76, 0xf4, 0x03, 0xdb, 0x9f, 0x89, 0x8d, 0x69, 0x42, 0xc1, 0x4a, 0x3d, 0x94, 0xea, 0x8d, 0x88, - 0x81, 0x17, 0x8d, 0x0b, 0x6d, 0xf4, 0x29, 0x2c, 0x79, 0xc4, 0xb6, 0x9c, 0xf1, 0x4b, 0xcd, 0xa0, - 0x8e, 0x4f, 0x5e, 0x06, 0xaf, 0x65, 0xd7, 0xe9, 0xed, 0x35, 0xb7, 0x99, 0x54, 0x5d, 0x08, 0xd5, - 0xd0, 0xf9, 0xd9, 0xfa, 0xe2, 0x45, 0x1a, 0x5e, 0x94, 0x9a, 0x65, 0xbb, 0xd4, 0x86, 0xc5, 0x8b, - 0xa3, 0x41, 0x2b, 0xf2, 0xec, 0x73, 0x17, 0x12, 0x9c, 0x6d, 0x74, 0x1b, 0x72, 0x2e, 0x19, 0x58, - 0x9e, 0xef, 0x0a, 0x33, 0x33, 0x4e, 0x48, 0x61, 0x27, 0x5f, 0x7c, 0x1d, 0x55, 0xfa, 0x15, 0x98, - 0xe9, 0x91, 0x1d, 0x16, 0xd3, 0xf2, 0xf4, 0x3d, 0xa9, 0x32, 0x87, 0x83, 0x26, 0xdb, 0x83, 0x63, - 0x2f, 0x0c, 0xd4, 0xf8, 0x6f, 0x46, 0xe3, 0x11, 0x85, 0xfc, 0x56, 0x8c, 0xc7, 0x0c, 0xc1, 0x47, - 0xa7, 0xe9, 0xc8, 0x47, 0xa7, 0x2b, 0x90, 0xb1, 0xc9, 0x11, 0xb1, 0xc5, 0x5d, 0x8e, 0x45, 0xe3, - 0xde, 0x43, 0x28, 0x06, 0x5f, 0x37, 0xf2, 0xaf, 0x2a, 0x72, 0x90, 0xee, 0x57, 0x7b, 0xcf, 0x94, - 0x39, 0x04, 0x90, 0x15, 0x9b, 0x53, 0xbc, 0xe4, 0xd5, 0x3b, 0xed, 0x8d, 0xd6, 0xa6, 0x92, 0xbc, - 0xf7, 0xbb, 0x69, 0xc8, 0x87, 0x6f, 0x49, 0xec, 0xee, 0x68, 0x37, 0x5f, 0x04, 0xbb, 0x3b, 0xa4, - 0xb7, 0xc9, 0x31, 0x7a, 0x7b, 0x5a, 0x85, 0xfa, 0x44, 0x3c, 0x9e, 0x87, 0xec, 0xa0, 0x02, 0xf5, - 0x2e, 0xe4, 0xaa, 0xbd, 0x5e, 0x6b, 0xb3, 0xdd, 0x6c, 0x28, 0x9f, 0x25, 0x4a, 0x5f, 0x38, 0x39, - 0x2d, 0x2f, 0x87, 0xa0, 0xaa, 0x27, 0x36, 0x1f, 0x47, 0xd5, 0xeb, 0xcd, 0x6e, 0xbf, 0xd9, 0x50, - 0x5e, 0x25, 0x67, 0x51, 0xbc, 0xaa, 0xc2, 0x3f, 0xeb, 0xc9, 0x77, 0x71, 0xb3, 0x5b, 0xc5, 0xac, - 0xc3, 0xcf, 0x92, 0xa2, 0x38, 0x36, 0xed, 0xd1, 0x25, 0x23, 0xdd, 0x65, 0x7d, 0xae, 0x05, 0xdf, - 0xc9, 0xbd, 0x4a, 0x89, 0x4f, 0x3f, 0xa6, 0x0f, 0x63, 0x44, 0x37, 0x27, 0xac, 0x37, 0xfe, 0x22, - 0xc9, 0xd5, 0xa4, 0x66, 0x7a, 0xeb, 0x31, 0xdf, 0xc3, 0xb4, 0xa8, 0x30, 0x8f, 0x77, 0xdb, 0x6d, - 0x06, 0x7a, 0x95, 0x9e, 0x99, 0x1d, 0x1e, 0x3b, 0x2c, 0x63, 0x46, 0x77, 0x21, 0x17, 0x3c, 0x58, - 0x2a, 0x9f, 0xa5, 0x67, 0x06, 0x54, 0x0f, 0x5e, 0x5b, 0x79, 0x87, 0x5b, 0xbb, 0x7d, 0xfe, 0x19, - 0xdf, 0xab, 0xcc, 0x6c, 0x87, 0x07, 0x63, 0xdf, 0xa4, 0xc7, 0x0e, 0x3b, 0xb3, 0xb2, 0x0e, 0xf7, - 0x59, 0x46, 0x14, 0x2d, 0x42, 0x8c, 0x2c, 0xc2, 0xbd, 0x0b, 0x39, 0xdc, 0xfc, 0x96, 0xf8, 0xe2, - 0xef, 0x55, 0x76, 0x46, 0x0f, 0x26, 0x9f, 0x12, 0x83, 0xf5, 0x56, 0x86, 0x2c, 0x6e, 0xee, 0x74, - 0x9e, 0x37, 0x95, 0x3f, 0xcc, 0xce, 0xe8, 0xc1, 0x64, 0x48, 0xf9, 0x77, 0x4f, 0xb9, 0x0e, 0xee, - 0x6e, 0x55, 0xf9, 0xa2, 0xcc, 0xea, 0xe9, 0xb8, 0xa3, 0x03, 0xdd, 0x21, 0xe6, 0xf4, 0x0b, 0x99, - 0x90, 0x75, 0xef, 0x17, 0x20, 0x17, 0xc4, 0xae, 0x68, 0x0d, 0xb2, 0x2f, 0x3a, 0xf8, 0x59, 0x13, - 0x2b, 0x73, 0xc2, 0xca, 0x01, 0xe7, 0x85, 0xc8, 0x3a, 0xca, 0x30, 0xbf, 0x53, 0x6d, 0x57, 0x37, - 0x9b, 0x38, 0x28, 0xa2, 0x07, 0x00, 0x19, 0x80, 0x95, 0x14, 0xd9, 0x41, 0xa8, 0xb3, 0xb6, 0xfa, - 0xfd, 0x1f, 0xad, 0xcd, 0xfd, 0xf0, 0x47, 0x6b, 0x73, 0xaf, 0xce, 0xd7, 0x12, 0xdf, 0x3f, 0x5f, - 0x4b, 0xfc, 0xfd, 0xf9, 0x5a, 0xe2, 0xdf, 0xce, 0xd7, 0x12, 0x7b, 0x59, 0x7e, 0x4d, 0x3c, 0xf9, - 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x9b, 0x2e, 0x8d, 0x2e, 0x32, 0x00, 0x00, + // 5116 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x7a, 0x5d, 0x6c, 0x23, 0x59, + 0x56, 0x7f, 0xec, 0xd8, 0x8e, 0x7d, 0xec, 0x24, 0xd5, 0xb7, 0xb3, 0x3d, 0x69, 0x6f, 0x4f, 0xe2, + 0xa9, 0x99, 0xde, 0x99, 0xed, 0x9d, 0xbf, 0xfb, 0x6b, 0x77, 0xd5, 0x33, 0xf3, 0xdf, 0x9d, 0xb1, + 0xcb, 0x95, 0x8e, 0xb7, 0xd3, 0xb6, 0x75, 0xed, 0x74, 0xef, 0x22, 0x41, 0x51, 0xa9, 0xba, 0x71, + 0x6a, 0x52, 0xae, 0x6b, 0xaa, 0xca, 0xe9, 0x36, 0x0b, 0x62, 0xc4, 0x03, 0xa0, 0x3c, 0xc1, 0x0b, + 0x2c, 0x42, 0x41, 0x48, 0xf0, 0xc6, 0x03, 0x0f, 0x20, 0x21, 0x78, 0x1a, 0x24, 0x84, 0x56, 0xbc, + 0xc0, 0x82, 0x84, 0x56, 0x20, 0x05, 0x36, 0x0f, 0xbc, 0xad, 0xe0, 0x05, 0xf1, 0xc2, 0x03, 0xba, + 0x1f, 0x55, 0xae, 0xb8, 0x2b, 0xc9, 0x0c, 0xbb, 0x2f, 0x89, 0xef, 0x39, 0xbf, 0x73, 0xee, 0xbd, + 0xe7, 0xde, 0x7b, 0xee, 0x39, 0xe7, 0x16, 0xdc, 0x19, 0x3a, 0xe1, 0xc1, 0x64, 0xaf, 0x6e, 0xd1, + 0xd1, 0x5d, 0x9b, 0x5a, 0x87, 0xc4, 0xbf, 0x1b, 0xbc, 0x30, 0xfd, 0xd1, 0xa1, 0x13, 0xde, 0x35, + 0xc7, 0xce, 0xdd, 0x70, 0x3a, 0x26, 0x41, 0x7d, 0xec, 0xd3, 0x90, 0x22, 0x24, 0x00, 0xf5, 0x08, + 0x50, 0x3f, 0xba, 0x5f, 0xdd, 0x1c, 0x52, 0x3a, 0x74, 0xc9, 0x5d, 0x8e, 0xd8, 0x9b, 0xec, 0xdf, + 0x0d, 0x9d, 0x11, 0x09, 0x42, 0x73, 0x34, 0x16, 0x42, 0xd5, 0x8d, 0x79, 0x80, 0x3d, 0xf1, 0xcd, + 0xd0, 0xa1, 0x9e, 0xe4, 0xaf, 0x0d, 0xe9, 0x90, 0xf2, 0x9f, 0x77, 0xd9, 0x2f, 0x41, 0x55, 0x37, + 0x61, 0xe9, 0x19, 0xf1, 0x03, 0x87, 0x7a, 0x68, 0x0d, 0xf2, 0x8e, 0x67, 0x93, 0x97, 0xeb, 0x99, + 0x5a, 0xe6, 0x9d, 0x1c, 0x16, 0x0d, 0xf5, 0x1e, 0x40, 0x9b, 0xfd, 0xd0, 0xbd, 0xd0, 0x9f, 0x22, + 0x05, 0x16, 0x0f, 0xc9, 0x94, 0x23, 0x4a, 0x98, 0xfd, 0x64, 0x94, 0x23, 0xd3, 0x5d, 0xcf, 0x0a, + 0xca, 0x91, 0xe9, 0xaa, 0x3f, 0xca, 0x40, 0xb9, 0xe1, 0x79, 0x34, 0xe4, 0xbd, 0x07, 0x08, 0x41, + 0xce, 0x33, 0x47, 0x44, 0x0a, 0xf1, 0xdf, 0x48, 0x83, 0x82, 0x6b, 0xee, 0x11, 0x37, 0x58, 0xcf, + 0xd6, 0x16, 0xdf, 0x29, 0x3f, 0xf8, 0x4a, 0xfd, 0xd5, 0x29, 0xd7, 0x13, 0x4a, 0xea, 0x3b, 0x1c, + 0xcd, 0x07, 0x81, 0xa5, 0x28, 0xfa, 0x26, 0x2c, 0x39, 0x9e, 0xed, 0x58, 0x24, 0x58, 0xcf, 0x71, + 0x2d, 0x1b, 0x69, 0x5a, 0x66, 0xa3, 0x6f, 0xe6, 0xbe, 0x7f, 0xba, 0xb9, 0x80, 0x23, 0xa1, 0xea, + 0x7b, 0x50, 0x4e, 0xa8, 0x4d, 0x99, 0xdb, 0x1a, 0xe4, 0x8f, 0x4c, 0x77, 0x42, 0xe4, 0xec, 0x44, + 0xe3, 0xfd, 0xec, 0xa3, 0x8c, 0xfa, 0x11, 0xac, 0x75, 0xcc, 0x11, 0xb1, 0x1f, 0x13, 0x8f, 0xf8, + 0x8e, 0x85, 0x49, 0x40, 0x27, 0xbe, 0x45, 0xd8, 0x5c, 0x0f, 0x1d, 0xcf, 0x8e, 0xe6, 0xca, 0x7e, + 0xa7, 0x6b, 0x51, 0x35, 0x78, 0xad, 0xe5, 0x04, 0x96, 0x4f, 0x42, 0xf2, 0xb9, 0x95, 0x2c, 0x46, + 0x4a, 0x4e, 0x33, 0xb0, 0x3a, 0x2f, 0xfd, 0x33, 0x70, 0x9d, 0x99, 0xd8, 0x36, 0x7c, 0x49, 0x31, + 0x82, 0x31, 0xb1, 0xb8, 0xb2, 0xf2, 0x83, 0x77, 0xd2, 0x2c, 0x94, 0x36, 0x93, 0xed, 0x05, 0x7c, + 0x8d, 0xab, 0x89, 0x08, 0xfd, 0x31, 0xb1, 0x90, 0x05, 0x37, 0x6c, 0x39, 0xe8, 0x39, 0xf5, 0x59, + 0xae, 0x3e, 0x75, 0x19, 0x2f, 0x98, 0xe6, 0xf6, 0x02, 0x5e, 0x8b, 0x94, 0x25, 0x3b, 0x69, 0x02, + 0x14, 0x23, 0xdd, 0xea, 0xf7, 0x32, 0x50, 0x8a, 0x98, 0x01, 0xfa, 0x32, 0x94, 0x3c, 0xd3, 0xa3, + 0x86, 0x35, 0x9e, 0x04, 0x7c, 0x42, 0x8b, 0xcd, 0xca, 0xd9, 0xe9, 0x66, 0xb1, 0x63, 0x7a, 0x54, + 0xeb, 0xed, 0x06, 0xb8, 0xc8, 0xd8, 0xda, 0x78, 0x12, 0xa0, 0x37, 0xa0, 0x32, 0x22, 0x23, 0xea, + 0x4f, 0x8d, 0xbd, 0x69, 0x48, 0x02, 0x69, 0xb6, 0xb2, 0xa0, 0x35, 0x19, 0x09, 0x7d, 0x03, 0x96, + 0x86, 0x62, 0x48, 0xeb, 0x8b, 0x7c, 0xfb, 0xbc, 0x99, 0x36, 0xfa, 0xb9, 0x51, 0xe3, 0x48, 0x46, + 0xfd, 0xcd, 0x0c, 0xac, 0xc5, 0x54, 0xf2, 0x0b, 0x13, 0xc7, 0x27, 0x23, 0xe2, 0x85, 0x01, 0xfa, + 0x1a, 0x14, 0x5c, 0x67, 0xe4, 0x84, 0x81, 0xb4, 0xf9, 0xeb, 0x69, 0x6a, 0xe3, 0x49, 0x61, 0x09, + 0x46, 0x0d, 0xa8, 0xf8, 0x24, 0x20, 0xfe, 0x91, 0xd8, 0xf1, 0xd2, 0xa2, 0x57, 0x08, 0x9f, 0x13, + 0x51, 0xb7, 0xa0, 0xd8, 0x73, 0xcd, 0x70, 0x9f, 0xfa, 0x23, 0xa4, 0x42, 0xc5, 0xf4, 0xad, 0x03, + 0x27, 0x24, 0x56, 0x38, 0xf1, 0xa3, 0xd3, 0x77, 0x8e, 0x86, 0x6e, 0x40, 0x96, 0x8a, 0x8e, 0x4a, + 0xcd, 0xc2, 0xd9, 0xe9, 0x66, 0xb6, 0xdb, 0xc7, 0x59, 0x1a, 0xa8, 0x1f, 0xc0, 0xb5, 0x9e, 0x3b, + 0x19, 0x3a, 0x5e, 0x8b, 0x04, 0x96, 0xef, 0x8c, 0x99, 0x76, 0xb6, 0x2b, 0x99, 0x8f, 0x8a, 0x76, + 0x25, 0xfb, 0x1d, 0x1f, 0xed, 0xec, 0xec, 0x68, 0xab, 0xbf, 0x9e, 0x85, 0x6b, 0xba, 0x37, 0x74, + 0x3c, 0x92, 0x94, 0xbe, 0x0d, 0x2b, 0x84, 0x13, 0x8d, 0x23, 0xe1, 0x6e, 0xa4, 0x9e, 0x65, 0x41, + 0x8d, 0x7c, 0x50, 0x7b, 0xce, 0x2f, 0xdc, 0x4f, 0x9b, 0xfe, 0x2b, 0xda, 0x53, 0xbd, 0x83, 0x0e, + 0x4b, 0x63, 0x3e, 0x89, 0x40, 0x2e, 0xef, 0xed, 0x34, 0x5d, 0xaf, 0xcc, 0x33, 0x72, 0x12, 0x52, + 0xf6, 0x27, 0x71, 0x12, 0x7f, 0x9b, 0x85, 0xd5, 0x0e, 0xb5, 0xcf, 0xd9, 0xa1, 0x0a, 0xc5, 0x03, + 0x1a, 0x84, 0x09, 0x87, 0x18, 0xb7, 0xd1, 0x23, 0x28, 0x8e, 0xe5, 0xf2, 0xc9, 0xd5, 0xbf, 0x95, + 0x3e, 0x64, 0x81, 0xc1, 0x31, 0x1a, 0x7d, 0x00, 0xa5, 0xe8, 0xc8, 0xb0, 0xd9, 0x7e, 0x86, 0x8d, + 0x33, 0xc3, 0xa3, 0x6f, 0x40, 0x41, 0x2c, 0xc2, 0x7a, 0x8e, 0x4b, 0xde, 0xfe, 0x4c, 0x36, 0xc7, + 0x52, 0x08, 0x3d, 0x86, 0x62, 0xe8, 0x06, 0x86, 0xe3, 0xed, 0xd3, 0xf5, 0x3c, 0x57, 0xb0, 0x99, + 0xea, 0x64, 0xa8, 0x4d, 0x06, 0x3b, 0xfd, 0xb6, 0xb7, 0x4f, 0x9b, 0xe5, 0xb3, 0xd3, 0xcd, 0x25, + 0xd9, 0xc0, 0x4b, 0xa1, 0x1b, 0xb0, 0x1f, 0xe8, 0x16, 0xe4, 0xf6, 0x9d, 0x71, 0xb0, 0x5e, 0xa8, + 0x65, 0xde, 0x29, 0x36, 0x8b, 0x67, 0xa7, 0x9b, 0xb9, 0xad, 0x76, 0xaf, 0x8f, 0x39, 0x55, 0xfd, + 0xad, 0x0c, 0x94, 0x13, 0x3a, 0xd0, 0xeb, 0x00, 0xa1, 0x3f, 0x09, 0x42, 0xc3, 0xa7, 0x34, 0xe4, + 0xa6, 0xac, 0xe0, 0x12, 0xa7, 0x60, 0x4a, 0x43, 0x54, 0x87, 0xeb, 0x16, 0xf1, 0x43, 0xc3, 0x09, + 0x82, 0x09, 0xf1, 0x8d, 0x60, 0xb2, 0xf7, 0x31, 0xb1, 0x42, 0x6e, 0xd6, 0x0a, 0xbe, 0xc6, 0x58, + 0x6d, 0xce, 0xe9, 0x0b, 0x06, 0x7a, 0x08, 0x37, 0x92, 0xf8, 0xf1, 0x64, 0xcf, 0x75, 0x2c, 0x83, + 0x2d, 0xf5, 0x22, 0x17, 0xb9, 0x3e, 0x13, 0xe9, 0x71, 0xde, 0x13, 0x32, 0x55, 0x7f, 0x98, 0x01, + 0x05, 0x9b, 0xfb, 0xe1, 0x53, 0x32, 0xda, 0x23, 0x7e, 0x3f, 0x34, 0xc3, 0x49, 0x80, 0x6e, 0x40, + 0xc1, 0x25, 0xa6, 0x4d, 0x7c, 0x3e, 0xa8, 0x22, 0x96, 0x2d, 0xb4, 0xcb, 0xce, 0xb7, 0x69, 0x1d, + 0x98, 0x7b, 0x8e, 0xeb, 0x84, 0x53, 0x3e, 0x94, 0x95, 0xf4, 0x0d, 0x3e, 0xaf, 0xb3, 0x8e, 0x13, + 0x82, 0xf8, 0x9c, 0x1a, 0xb4, 0x0e, 0x4b, 0x23, 0x12, 0x04, 0xe6, 0x90, 0xf0, 0x91, 0x96, 0x70, + 0xd4, 0x54, 0x3f, 0x80, 0x4a, 0x52, 0x0e, 0x95, 0x61, 0x69, 0xb7, 0xf3, 0xa4, 0xd3, 0x7d, 0xde, + 0x51, 0x16, 0xd0, 0x2a, 0x94, 0x77, 0x3b, 0x58, 0x6f, 0x68, 0xdb, 0x8d, 0xe6, 0x8e, 0xae, 0x64, + 0xd0, 0x32, 0x94, 0x66, 0xcd, 0xac, 0xfa, 0xa7, 0x19, 0x00, 0x66, 0x6e, 0x39, 0xa9, 0xf7, 0x21, + 0x1f, 0x84, 0x66, 0x28, 0xf6, 0xec, 0xca, 0x83, 0xb7, 0x2e, 0x5a, 0x61, 0x39, 0x5e, 0xf6, 0x8f, + 0x60, 0x21, 0x92, 0x1c, 0x61, 0xf6, 0xdc, 0x08, 0x99, 0xfb, 0x30, 0x6d, 0xdb, 0x97, 0x03, 0xe7, + 0xbf, 0xd5, 0x0f, 0x20, 0xcf, 0xa5, 0xcf, 0x0f, 0xb7, 0x08, 0xb9, 0x16, 0xfb, 0x95, 0x41, 0x25, + 0xc8, 0x63, 0xbd, 0xd1, 0xfa, 0x8e, 0x92, 0x45, 0x0a, 0x54, 0x5a, 0xed, 0xbe, 0xd6, 0xed, 0x74, + 0x74, 0x6d, 0xa0, 0xb7, 0x94, 0x45, 0xf5, 0x36, 0xe4, 0xdb, 0x23, 0xa6, 0xf9, 0x16, 0x3b, 0x10, + 0xfb, 0xc4, 0x27, 0x9e, 0x15, 0x9d, 0xb3, 0x19, 0x41, 0xfd, 0x71, 0x19, 0xf2, 0x4f, 0xe9, 0xc4, + 0x0b, 0xd1, 0x83, 0x84, 0x53, 0x5b, 0x49, 0x8f, 0x1f, 0x38, 0xb0, 0x3e, 0x98, 0x8e, 0x89, 0x74, + 0x7a, 0x37, 0xa0, 0x20, 0x8e, 0x8e, 0x9c, 0x8e, 0x6c, 0x31, 0x7a, 0x68, 0xfa, 0x43, 0x12, 0xca, + 0xf9, 0xc8, 0x16, 0x7a, 0x87, 0xdd, 0x67, 0xa6, 0x4d, 0x3d, 0x77, 0xca, 0x4f, 0x58, 0x51, 0x5c, + 0x5a, 0x98, 0x98, 0x76, 0xd7, 0x73, 0xa7, 0x38, 0xe6, 0xa2, 0x6d, 0xa8, 0xec, 0x39, 0x9e, 0x6d, + 0xd0, 0xb1, 0xb8, 0x02, 0xf2, 0x17, 0x9f, 0x47, 0x31, 0xaa, 0xa6, 0xe3, 0xd9, 0x5d, 0x01, 0xc6, + 0xe5, 0xbd, 0x59, 0x03, 0x75, 0x60, 0xe5, 0x88, 0xba, 0x93, 0x11, 0x89, 0x75, 0x15, 0xb8, 0xae, + 0xb7, 0x2f, 0xd6, 0xf5, 0x8c, 0xe3, 0x23, 0x6d, 0xcb, 0x47, 0xc9, 0x26, 0x7a, 0x02, 0xcb, 0xe1, + 0x68, 0xbc, 0x1f, 0xc4, 0xea, 0x96, 0xb8, 0xba, 0x2f, 0x5d, 0x62, 0x30, 0x06, 0x8f, 0xb4, 0x55, + 0xc2, 0x44, 0x0b, 0x3d, 0x86, 0xb2, 0x45, 0xbd, 0xc0, 0x09, 0x42, 0xe2, 0x59, 0xd3, 0xf5, 0x22, + 0xb7, 0xfd, 0x25, 0xb3, 0xd4, 0x66, 0x60, 0x9c, 0x94, 0xac, 0xfe, 0xea, 0x22, 0x94, 0x13, 0x26, + 0x40, 0x7d, 0x28, 0x8f, 0x7d, 0x3a, 0x36, 0x87, 0xfc, 0x3e, 0x94, 0x8b, 0x7a, 0xff, 0x33, 0x99, + 0xaf, 0xde, 0x9b, 0x09, 0xe2, 0xa4, 0x16, 0xf5, 0x24, 0x0b, 0xe5, 0x04, 0x13, 0xdd, 0x81, 0x22, + 0xee, 0xe1, 0xf6, 0xb3, 0xc6, 0x40, 0x57, 0x16, 0xaa, 0xb7, 0x8e, 0x4f, 0x6a, 0xeb, 0x5c, 0x5b, + 0x52, 0x41, 0xcf, 0x77, 0x8e, 0xd8, 0x1e, 0x7e, 0x07, 0x96, 0x22, 0x68, 0xa6, 0xfa, 0xc5, 0xe3, + 0x93, 0xda, 0x6b, 0xf3, 0xd0, 0x04, 0x12, 0xf7, 0xb7, 0x1b, 0x58, 0x6f, 0x29, 0xd9, 0x74, 0x24, + 0xee, 0x1f, 0x98, 0x3e, 0xb1, 0xd1, 0x97, 0xa0, 0x20, 0x81, 0x8b, 0xd5, 0xea, 0xf1, 0x49, 0xed, + 0xc6, 0x3c, 0x70, 0x86, 0xc3, 0xfd, 0x9d, 0xc6, 0x33, 0x5d, 0xc9, 0xa5, 0xe3, 0x70, 0xdf, 0x35, + 0x8f, 0x08, 0x7a, 0x0b, 0xf2, 0x02, 0x96, 0xaf, 0xde, 0x3c, 0x3e, 0xa9, 0x7d, 0xe1, 0x15, 0x75, + 0x0c, 0x55, 0x5d, 0xff, 0x8d, 0x3f, 0xdc, 0x58, 0xf8, 0xcb, 0x3f, 0xda, 0x50, 0xe6, 0xd9, 0xd5, + 0xff, 0xc9, 0xc0, 0xf2, 0xb9, 0xbd, 0x83, 0x54, 0x28, 0x78, 0xd4, 0xa2, 0x63, 0x71, 0x4d, 0x16, + 0x9b, 0x70, 0x76, 0xba, 0x59, 0xe8, 0x50, 0x8d, 0x8e, 0xa7, 0x58, 0x72, 0xd0, 0x93, 0xb9, 0x8b, + 0xfe, 0xe1, 0x67, 0xdc, 0x98, 0xa9, 0x57, 0xfd, 0x87, 0xb0, 0x6c, 0xfb, 0xce, 0x11, 0xf1, 0x0d, + 0x8b, 0x7a, 0xfb, 0xce, 0x50, 0x5e, 0x81, 0xd5, 0xd4, 0x68, 0x94, 0x03, 0x71, 0x45, 0x08, 0x68, + 0x1c, 0xff, 0x13, 0x5c, 0xf2, 0xd5, 0x67, 0x50, 0x49, 0x6e, 0x75, 0x76, 0x2f, 0x05, 0xce, 0x2f, + 0x12, 0x19, 0x76, 0xf2, 0x20, 0x15, 0x97, 0x18, 0x45, 0x04, 0x9d, 0x6f, 0x43, 0x6e, 0x44, 0x6d, + 0xa1, 0x67, 0xb9, 0x79, 0x9d, 0xc5, 0x1a, 0xff, 0x7c, 0xba, 0x59, 0xa6, 0x41, 0x7d, 0xcb, 0x71, + 0xc9, 0x53, 0x6a, 0x13, 0xcc, 0x01, 0xea, 0x11, 0xe4, 0x98, 0xcf, 0x41, 0x5f, 0x84, 0x5c, 0xb3, + 0xdd, 0x69, 0x29, 0x0b, 0xd5, 0x6b, 0xc7, 0x27, 0xb5, 0x65, 0x6e, 0x12, 0xc6, 0x60, 0x7b, 0x17, + 0x6d, 0x42, 0xe1, 0x59, 0x77, 0x67, 0xf7, 0x29, 0xdb, 0x5e, 0xd7, 0x8f, 0x4f, 0x6a, 0xab, 0x31, + 0x5b, 0x18, 0x0d, 0xbd, 0x0e, 0xf9, 0xc1, 0xd3, 0xde, 0x56, 0x5f, 0xc9, 0x56, 0xd1, 0xf1, 0x49, + 0x6d, 0x25, 0xe6, 0xf3, 0x31, 0x57, 0xaf, 0xc9, 0x55, 0x2d, 0xc5, 0x74, 0xf5, 0x07, 0x19, 0x28, + 0x27, 0x0e, 0x1c, 0xdb, 0x98, 0x2d, 0x7d, 0xab, 0xb1, 0xbb, 0x33, 0x50, 0x16, 0x12, 0x1b, 0x33, + 0x01, 0x69, 0x91, 0x7d, 0x73, 0xe2, 0x32, 0x3f, 0x07, 0x5a, 0xb7, 0xd3, 0x6f, 0xf7, 0x07, 0x7a, + 0x67, 0xa0, 0x64, 0xaa, 0xeb, 0xc7, 0x27, 0xb5, 0xb5, 0x79, 0xf0, 0xd6, 0xc4, 0x75, 0xd9, 0xd6, + 0xd4, 0x1a, 0xda, 0x36, 0xdf, 0xeb, 0xb3, 0xad, 0x99, 0x40, 0x69, 0xa6, 0x75, 0x40, 0x6c, 0xf4, + 0x2e, 0x94, 0x5a, 0xfa, 0x8e, 0xfe, 0xb8, 0xc1, 0xbd, 0x7b, 0xf5, 0xf5, 0xe3, 0x93, 0xda, 0xcd, + 0x57, 0x7b, 0x77, 0xc9, 0xd0, 0x0c, 0x89, 0x3d, 0xb7, 0x45, 0x13, 0x10, 0xf5, 0xbf, 0xb2, 0xb0, + 0x8c, 0x59, 0xb2, 0xec, 0x87, 0x3d, 0xea, 0x3a, 0xd6, 0x14, 0xf5, 0xa0, 0x64, 0x51, 0xcf, 0x76, + 0x12, 0x7e, 0xe2, 0xc1, 0x05, 0x01, 0xd3, 0x4c, 0x2a, 0x6a, 0x69, 0x91, 0x24, 0x9e, 0x29, 0x41, + 0x77, 0x21, 0x6f, 0x13, 0xd7, 0x9c, 0xca, 0xc8, 0xed, 0x66, 0x5d, 0xa4, 0xe3, 0xf5, 0x28, 0x1d, + 0xaf, 0xb7, 0x64, 0x3a, 0x8e, 0x05, 0x8e, 0x67, 0x28, 0xe6, 0x4b, 0xc3, 0x0c, 0x43, 0x32, 0x1a, + 0x87, 0x22, 0x6c, 0xcb, 0xe1, 0xf2, 0xc8, 0x7c, 0xd9, 0x90, 0x24, 0x74, 0x1f, 0x0a, 0x2f, 0x1c, + 0xcf, 0xa6, 0x2f, 0x64, 0x64, 0x76, 0x89, 0x52, 0x09, 0x54, 0x8f, 0x59, 0x48, 0x32, 0x37, 0x4c, + 0xb6, 0x87, 0x3a, 0xdd, 0x8e, 0x1e, 0xed, 0x21, 0xc9, 0xef, 0x7a, 0x1d, 0xea, 0xb1, 0xf3, 0x0f, + 0xdd, 0x8e, 0xb1, 0xd5, 0x68, 0xef, 0xec, 0x62, 0xb6, 0x8f, 0xd6, 0x8e, 0x4f, 0x6a, 0x4a, 0x0c, + 0xd9, 0x32, 0x1d, 0x97, 0xa5, 0x0a, 0x37, 0x61, 0xb1, 0xd1, 0xf9, 0x8e, 0x92, 0xad, 0x2a, 0xc7, + 0x27, 0xb5, 0x4a, 0xcc, 0x6e, 0x78, 0xd3, 0x99, 0xdd, 0xe7, 0xfb, 0x55, 0xff, 0x6e, 0x11, 0x2a, + 0xbb, 0x63, 0xdb, 0x0c, 0x89, 0x38, 0x67, 0xa8, 0x06, 0xe5, 0xb1, 0xe9, 0x9b, 0xae, 0x4b, 0x5c, + 0x27, 0x18, 0xc9, 0x42, 0x43, 0x92, 0x84, 0xde, 0xfb, 0xac, 0x66, 0x6c, 0x16, 0xd9, 0xd9, 0xf9, + 0xde, 0xbf, 0x6e, 0x66, 0x22, 0x83, 0xee, 0xc2, 0xca, 0xbe, 0x18, 0xad, 0x61, 0x5a, 0x7c, 0x61, + 0x17, 0xf9, 0xc2, 0xd6, 0xd3, 0x16, 0x36, 0x39, 0xac, 0xba, 0x9c, 0x64, 0x83, 0x4b, 0xe1, 0xe5, + 0xfd, 0x64, 0x13, 0x3d, 0x84, 0xa5, 0x11, 0xf5, 0x9c, 0x90, 0xfa, 0x57, 0xaf, 0x42, 0x84, 0x44, + 0x77, 0xe0, 0x1a, 0x5b, 0xdc, 0x68, 0x3c, 0x9c, 0xcd, 0xaf, 0xf3, 0x2c, 0x5e, 0x1d, 0x99, 0x2f, + 0x65, 0x87, 0x98, 0x91, 0x51, 0x13, 0xf2, 0xd4, 0x67, 0xf1, 0x62, 0x81, 0x0f, 0xf7, 0xdd, 0x2b, + 0x87, 0x2b, 0x1a, 0x5d, 0x26, 0x83, 0x85, 0xa8, 0xfa, 0x75, 0x58, 0x3e, 0x37, 0x09, 0x16, 0x26, + 0xf5, 0x1a, 0xbb, 0x7d, 0x5d, 0x59, 0x40, 0x15, 0x28, 0x6a, 0xdd, 0xce, 0xa0, 0xdd, 0xd9, 0x65, + 0x71, 0x5e, 0x05, 0x8a, 0xb8, 0xbb, 0xb3, 0xd3, 0x6c, 0x68, 0x4f, 0x94, 0xac, 0x5a, 0x87, 0x72, + 0x42, 0x1b, 0x5a, 0x01, 0xe8, 0x0f, 0xba, 0x3d, 0x63, 0xab, 0x8d, 0xfb, 0x03, 0x11, 0x25, 0xf6, + 0x07, 0x0d, 0x3c, 0x90, 0x84, 0x8c, 0xfa, 0x1f, 0xd9, 0x68, 0x45, 0x65, 0x60, 0xd8, 0x3c, 0x1f, + 0x18, 0x5e, 0x32, 0x78, 0x19, 0x1a, 0xce, 0x1a, 0x71, 0x80, 0xf8, 0x1e, 0x00, 0xdf, 0x38, 0xc4, + 0x36, 0xcc, 0x50, 0x2e, 0x7c, 0xf5, 0x15, 0x23, 0x0f, 0xa2, 0x7a, 0x17, 0x2e, 0x49, 0x74, 0x23, + 0x44, 0xdf, 0x80, 0x8a, 0x45, 0x47, 0x63, 0x97, 0x48, 0xe1, 0xc5, 0x2b, 0x85, 0xcb, 0x31, 0xbe, + 0x11, 0x26, 0x43, 0xd3, 0xdc, 0xf9, 0xe0, 0xf9, 0xd7, 0x32, 0x91, 0x65, 0x52, 0xa2, 0xd1, 0x0a, + 0x14, 0x77, 0x7b, 0xad, 0xc6, 0xa0, 0xdd, 0x79, 0xac, 0x64, 0x10, 0x40, 0x81, 0x9b, 0xba, 0xa5, + 0x64, 0x59, 0x14, 0xad, 0x75, 0x9f, 0xf6, 0x76, 0x74, 0xee, 0xb1, 0xd0, 0x1a, 0x28, 0x91, 0xb1, + 0x0d, 0x6e, 0x48, 0xbd, 0xa5, 0xe4, 0xd0, 0x75, 0x58, 0x8d, 0xa9, 0x52, 0x32, 0x8f, 0x6e, 0x00, + 0x8a, 0x89, 0x33, 0x15, 0x05, 0xf5, 0x97, 0x61, 0x55, 0xa3, 0x5e, 0x68, 0x3a, 0x5e, 0x9c, 0x61, + 0x3c, 0x60, 0x93, 0x96, 0x24, 0xc3, 0x91, 0x75, 0xa2, 0xe6, 0xea, 0xd9, 0xe9, 0x66, 0x39, 0x86, + 0xb6, 0x5b, 0x3c, 0x54, 0x92, 0x0d, 0x9b, 0x9d, 0xdf, 0xb1, 0x63, 0x73, 0xe3, 0xe6, 0x9b, 0x4b, + 0x67, 0xa7, 0x9b, 0x8b, 0xbd, 0x76, 0x0b, 0x33, 0x1a, 0xfa, 0x22, 0x94, 0xc8, 0x4b, 0x27, 0x34, + 0x2c, 0x76, 0x2f, 0x31, 0x03, 0xe6, 0x71, 0x91, 0x11, 0x34, 0x76, 0x0d, 0x35, 0x01, 0x7a, 0xd4, + 0x0f, 0x65, 0xcf, 0x5f, 0x85, 0xfc, 0x98, 0xfa, 0xbc, 0xb2, 0x71, 0x61, 0xbd, 0x8d, 0xc1, 0xc5, + 0x46, 0xc5, 0x02, 0xac, 0xfe, 0xee, 0x22, 0xc0, 0xc0, 0x0c, 0x0e, 0xa5, 0x92, 0x47, 0x50, 0x8a, + 0x6b, 0x97, 0xb2, 0x44, 0x72, 0xe9, 0x6a, 0xc7, 0x60, 0xf4, 0x30, 0xda, 0x6c, 0x22, 0x77, 0x4a, + 0x4d, 0x71, 0xa3, 0x8e, 0xd2, 0xd2, 0x8f, 0xf3, 0x09, 0x12, 0xbb, 0xe6, 0x89, 0xef, 0xcb, 0x95, + 0x67, 0x3f, 0x91, 0xc6, 0xaf, 0x05, 0x61, 0x34, 0x19, 0x7d, 0xa7, 0x16, 0x85, 0xe6, 0x56, 0x64, + 0x7b, 0x01, 0xcf, 0xe4, 0xd0, 0x87, 0x50, 0x66, 0xf3, 0x36, 0x02, 0xce, 0x93, 0x81, 0xf7, 0x85, + 0xa6, 0x12, 0x1a, 0x30, 0x8c, 0x67, 0x56, 0x7e, 0x1d, 0xc0, 0x1c, 0x8f, 0x5d, 0x87, 0xd8, 0xc6, + 0xde, 0x94, 0x47, 0xda, 0x25, 0x5c, 0x92, 0x94, 0xe6, 0x94, 0x1d, 0x97, 0x88, 0x6d, 0x86, 0x3c, + 0x7a, 0xbe, 0xc2, 0x80, 0x12, 0xdd, 0x08, 0x9b, 0x0a, 0xac, 0xf8, 0x13, 0x8f, 0x19, 0x54, 0x8e, + 0x4e, 0xfd, 0x93, 0x2c, 0xbc, 0xd6, 0x21, 0xe1, 0x0b, 0xea, 0x1f, 0x36, 0xc2, 0xd0, 0xb4, 0x0e, + 0x46, 0xc4, 0x93, 0xcb, 0x97, 0x48, 0x68, 0x32, 0xe7, 0x12, 0x9a, 0x75, 0x58, 0x32, 0x5d, 0xc7, + 0x0c, 0x88, 0x08, 0xde, 0x4a, 0x38, 0x6a, 0xb2, 0xb4, 0x8b, 0x25, 0x71, 0x24, 0x08, 0x88, 0xa8, + 0xba, 0xb0, 0x81, 0x47, 0x04, 0xf4, 0x5d, 0xb8, 0x21, 0xc3, 0x34, 0x33, 0xee, 0x8a, 0x25, 0x14, + 0x51, 0xf9, 0x56, 0x4f, 0xcd, 0x2a, 0xd3, 0x07, 0x27, 0xe3, 0xb8, 0x19, 0xb9, 0x3b, 0x0e, 0x65, + 0x54, 0xb8, 0x66, 0xa7, 0xb0, 0xaa, 0x8f, 0xe1, 0xe6, 0x85, 0x22, 0x9f, 0xab, 0xaa, 0xf3, 0x8f, + 0x59, 0x80, 0x76, 0xaf, 0xf1, 0x54, 0x1a, 0xa9, 0x05, 0x85, 0x7d, 0x73, 0xe4, 0xb8, 0xd3, 0xcb, + 0x3c, 0xe0, 0x0c, 0x5f, 0x6f, 0x08, 0x73, 0x6c, 0x71, 0x19, 0x2c, 0x65, 0x79, 0x4e, 0x39, 0xd9, + 0xf3, 0x48, 0x18, 0xe7, 0x94, 0xbc, 0xc5, 0x86, 0xe1, 0x9b, 0x5e, 0xbc, 0x75, 0x45, 0x83, 0x2d, + 0x00, 0x0b, 0x79, 0x5e, 0x98, 0xd3, 0xc8, 0x6d, 0xc9, 0x26, 0xda, 0xe6, 0xb5, 0x53, 0xe2, 0x1f, + 0x11, 0x7b, 0x3d, 0xcf, 0x8d, 0x7a, 0xd5, 0x78, 0xb0, 0x84, 0x0b, 0xdb, 0xc5, 0xd2, 0xd5, 0x0f, + 0x78, 0xc8, 0x34, 0x63, 0x7d, 0x2e, 0x1b, 0xdd, 0x83, 0xe5, 0x73, 0xf3, 0x7c, 0x25, 0x99, 0x6f, + 0xf7, 0x9e, 0x7d, 0x55, 0xc9, 0xc9, 0x5f, 0x5f, 0x57, 0x0a, 0xea, 0xdf, 0x2c, 0x0a, 0x47, 0x23, + 0xad, 0x9a, 0xfe, 0x66, 0x50, 0xe4, 0xbb, 0xdb, 0xa2, 0xae, 0x74, 0x00, 0x6f, 0x5f, 0xee, 0x7f, + 0x58, 0x4e, 0xc7, 0xe1, 0x38, 0x16, 0x44, 0x9b, 0x50, 0x16, 0xbb, 0xd8, 0x60, 0x07, 0x8e, 0x9b, + 0x75, 0x19, 0x83, 0x20, 0x31, 0x49, 0x74, 0x1b, 0x56, 0x78, 0xf1, 0x27, 0x38, 0x20, 0xb6, 0xc0, + 0xe4, 0x38, 0x66, 0x39, 0xa6, 0x72, 0xd8, 0x53, 0xa8, 0x48, 0x82, 0xc1, 0xe3, 0xf9, 0x3c, 0x1f, + 0xd0, 0x9d, 0xab, 0x06, 0x24, 0x44, 0x78, 0x98, 0x5f, 0x1e, 0xcf, 0x1a, 0xea, 0xcf, 0x43, 0x31, + 0x1a, 0x2c, 0x5a, 0x87, 0xc5, 0x81, 0xd6, 0x53, 0x16, 0xaa, 0xab, 0xc7, 0x27, 0xb5, 0x72, 0x44, + 0x1e, 0x68, 0x3d, 0xc6, 0xd9, 0x6d, 0xf5, 0x94, 0xcc, 0x79, 0xce, 0x6e, 0xab, 0x87, 0xaa, 0x90, + 0xeb, 0x6b, 0x83, 0x5e, 0x14, 0x9f, 0x45, 0x2c, 0x46, 0xab, 0xe6, 0x58, 0x7c, 0xa6, 0xee, 0x43, + 0x39, 0xd1, 0x3b, 0x7a, 0x13, 0x96, 0xda, 0x9d, 0xc7, 0x58, 0xef, 0xf7, 0x95, 0x85, 0xea, 0x8d, + 0xe3, 0x93, 0x1a, 0x4a, 0x70, 0xdb, 0xde, 0x90, 0xad, 0x1d, 0x7a, 0x1d, 0x72, 0xdb, 0x5d, 0x76, + 0xef, 0x8b, 0xe4, 0x22, 0x81, 0xd8, 0xa6, 0x41, 0x58, 0xbd, 0x2e, 0x03, 0xbf, 0xa4, 0x62, 0xf5, + 0xf7, 0x32, 0x50, 0x10, 0x07, 0x2d, 0x75, 0x11, 0x1b, 0xb0, 0x14, 0x95, 0x10, 0x44, 0xe2, 0xf7, + 0xf6, 0xc5, 0x49, 0x5a, 0x5d, 0xe6, 0x54, 0x62, 0x6b, 0x46, 0x72, 0xd5, 0xf7, 0xa1, 0x92, 0x64, + 0x7c, 0xae, 0x8d, 0xf9, 0x5d, 0x28, 0xb3, 0xbd, 0x1f, 0x25, 0x6b, 0x0f, 0xa0, 0x20, 0x9c, 0x45, + 0x7c, 0x0f, 0x5d, 0x9c, 0x31, 0x4a, 0x24, 0x7a, 0x04, 0x4b, 0x22, 0xcb, 0x8c, 0xea, 0xca, 0x1b, + 0x97, 0x9f, 0x30, 0x1c, 0xc1, 0xd5, 0x0f, 0x21, 0xd7, 0x23, 0xc4, 0x67, 0xb6, 0xf7, 0xa8, 0x4d, + 0x66, 0x57, 0xb7, 0x4c, 0x90, 0x6d, 0xd2, 0x6e, 0xb1, 0x04, 0xd9, 0x26, 0x6d, 0x3b, 0xae, 0x8d, + 0x65, 0x13, 0xb5, 0xb1, 0x01, 0x54, 0x9e, 0x13, 0x67, 0x78, 0x10, 0x12, 0x9b, 0x2b, 0x7a, 0x17, + 0x72, 0x63, 0x12, 0x0f, 0x7e, 0x3d, 0x75, 0xf3, 0x11, 0xe2, 0x63, 0x8e, 0x62, 0x3e, 0xe6, 0x05, + 0x97, 0x96, 0x8f, 0x21, 0xb2, 0xa5, 0xfe, 0x43, 0x16, 0x56, 0xda, 0x41, 0x30, 0x31, 0x3d, 0x2b, + 0x8a, 0xea, 0xbe, 0x79, 0x3e, 0xaa, 0x4b, 0x7d, 0x35, 0x3a, 0x2f, 0x72, 0xbe, 0xe4, 0x27, 0x6f, + 0xd6, 0x6c, 0x7c, 0xb3, 0xaa, 0x3f, 0xce, 0x44, 0x75, 0xbd, 0xdb, 0x09, 0x57, 0x20, 0x72, 0xc4, + 0xa4, 0x26, 0xb2, 0xeb, 0x1d, 0x7a, 0xf4, 0x85, 0x87, 0xde, 0x80, 0x3c, 0xd6, 0x3b, 0xfa, 0x73, + 0x25, 0x23, 0xb6, 0xe7, 0x39, 0x10, 0x26, 0x1e, 0x79, 0xc1, 0x34, 0xf5, 0xf4, 0x4e, 0x8b, 0x45, + 0x61, 0xd9, 0x14, 0x4d, 0x3d, 0xe2, 0xd9, 0x8e, 0x37, 0x44, 0x6f, 0x42, 0xa1, 0xdd, 0xef, 0xef, + 0xf2, 0x14, 0xf2, 0xb5, 0xe3, 0x93, 0xda, 0xf5, 0x73, 0x28, 0x5e, 0xd3, 0xb5, 0x19, 0x88, 0xa5, + 0x40, 0x2c, 0x3e, 0x4b, 0x01, 0xb1, 0xd8, 0x5a, 0x80, 0x70, 0x77, 0xd0, 0x18, 0xe8, 0x4a, 0x3e, + 0x05, 0x84, 0x29, 0xfb, 0x2b, 0x8f, 0xdb, 0xbf, 0x64, 0x41, 0x69, 0x58, 0x16, 0x19, 0x87, 0x8c, + 0x2f, 0xb3, 0xce, 0x01, 0x14, 0xc7, 0xec, 0x97, 0x43, 0xa2, 0x08, 0xea, 0x51, 0xea, 0xbb, 0xe7, + 0x9c, 0x5c, 0x1d, 0x53, 0x97, 0x34, 0xec, 0x91, 0x13, 0x04, 0x0e, 0xf5, 0x04, 0x0d, 0xc7, 0x9a, + 0xaa, 0xff, 0x99, 0x81, 0xeb, 0x29, 0x08, 0x74, 0x0f, 0x72, 0x3e, 0x75, 0xa3, 0x35, 0xbc, 0x75, + 0x51, 0xc9, 0x96, 0x89, 0x62, 0x8e, 0x44, 0x1b, 0x00, 0xe6, 0x24, 0xa4, 0x26, 0xef, 0x9f, 0xaf, + 0x5e, 0x11, 0x27, 0x28, 0xe8, 0x39, 0x14, 0x02, 0x62, 0xf9, 0x24, 0x8a, 0xb3, 0x3f, 0xfc, 0xbf, + 0x8e, 0xbe, 0xde, 0xe7, 0x6a, 0xb0, 0x54, 0x57, 0xad, 0x43, 0x41, 0x50, 0xd8, 0xb6, 0xb7, 0xcd, + 0xd0, 0x94, 0x05, 0x7d, 0xfe, 0x9b, 0xed, 0x26, 0xd3, 0x1d, 0x46, 0xbb, 0xc9, 0x74, 0x87, 0xea, + 0x5f, 0x67, 0x01, 0xf4, 0x97, 0x21, 0xf1, 0x3d, 0xd3, 0xd5, 0x1a, 0x48, 0x4f, 0xdc, 0x0c, 0x62, + 0xb6, 0x5f, 0x4e, 0x7d, 0xc3, 0x88, 0x25, 0xea, 0x5a, 0x23, 0xe5, 0x6e, 0xb8, 0x09, 0x8b, 0x13, + 0x5f, 0x3e, 0x65, 0x8b, 0x18, 0x79, 0x17, 0xef, 0x60, 0x46, 0x43, 0xfa, 0xcc, 0x6d, 0x2d, 0x5e, + 0xfc, 0x60, 0x9d, 0xe8, 0x20, 0xd5, 0x75, 0xb1, 0x93, 0x6f, 0x99, 0x86, 0x45, 0xe4, 0xad, 0x52, + 0x11, 0x27, 0x5f, 0x6b, 0x68, 0xc4, 0x0f, 0x71, 0xc1, 0x32, 0xd9, 0xff, 0x9f, 0xc8, 0xbf, 0xbd, + 0x0b, 0x30, 0x9b, 0x1a, 0xda, 0x80, 0xbc, 0xb6, 0xd5, 0xef, 0xef, 0x28, 0x0b, 0xc2, 0x81, 0xcf, + 0x58, 0x9c, 0xac, 0xfe, 0x45, 0x16, 0x8a, 0x5a, 0x43, 0x5e, 0xb9, 0x1a, 0x28, 0xdc, 0x2b, 0xf1, + 0x67, 0x10, 0xf2, 0x72, 0xec, 0xf8, 0x53, 0xe9, 0x58, 0x2e, 0x49, 0x78, 0x57, 0x98, 0x08, 0x1b, + 0xb5, 0xce, 0x05, 0x10, 0x86, 0x0a, 0x91, 0x46, 0x30, 0x2c, 0x33, 0xf2, 0xf1, 0x1b, 0x97, 0x1b, + 0x4b, 0xa4, 0x2e, 0xb3, 0x76, 0x80, 0xcb, 0x91, 0x12, 0xcd, 0x0c, 0xd0, 0x7b, 0xb0, 0x1a, 0x38, + 0x43, 0xcf, 0xf1, 0x86, 0x46, 0x64, 0x3c, 0xfe, 0x26, 0xd3, 0xbc, 0x76, 0x76, 0xba, 0xb9, 0xdc, + 0x17, 0x2c, 0x69, 0xc3, 0x65, 0x89, 0xd4, 0xb8, 0x29, 0xd1, 0xd7, 0x61, 0x25, 0x21, 0xca, 0xac, + 0x28, 0xcc, 0xae, 0x9c, 0x9d, 0x6e, 0x56, 0x62, 0xc9, 0x27, 0x64, 0x8a, 0x2b, 0xb1, 0xe0, 0x13, + 0xc2, 0x6b, 0x33, 0xfb, 0xd4, 0xb7, 0x88, 0xe1, 0xf3, 0x33, 0xcd, 0x6f, 0xf7, 0x1c, 0x2e, 0x73, + 0x9a, 0x38, 0xe6, 0xea, 0x33, 0xb8, 0xde, 0xf5, 0xad, 0x03, 0x12, 0x84, 0xc2, 0x14, 0xd2, 0x8a, + 0x1f, 0xc2, 0xad, 0xd0, 0x0c, 0x0e, 0x8d, 0x03, 0x27, 0x08, 0xa9, 0x3f, 0x35, 0x7c, 0x12, 0x12, + 0x8f, 0xf1, 0x0d, 0xfe, 0xcc, 0x2b, 0x0b, 0x82, 0x37, 0x19, 0x66, 0x5b, 0x40, 0x70, 0x84, 0xd8, + 0x61, 0x00, 0xb5, 0x0d, 0x15, 0x96, 0xc2, 0xc8, 0xa2, 0x1a, 0x9b, 0x3d, 0xb8, 0x74, 0x68, 0x7c, + 0xe6, 0x6b, 0xaa, 0xe4, 0xd2, 0xa1, 0xf8, 0xa9, 0x7e, 0x1b, 0x94, 0x96, 0x13, 0x8c, 0xcd, 0xd0, + 0x3a, 0x88, 0x2a, 0x9d, 0xa8, 0x05, 0xca, 0x01, 0x31, 0xfd, 0x70, 0x8f, 0x98, 0xa1, 0x31, 0x26, + 0xbe, 0x43, 0xed, 0xab, 0x57, 0x79, 0x35, 0x16, 0xe9, 0x71, 0x09, 0xf5, 0xbf, 0x33, 0x00, 0xd8, + 0xdc, 0x8f, 0xa2, 0xb5, 0xaf, 0xc0, 0xb5, 0xc0, 0x33, 0xc7, 0xc1, 0x01, 0x0d, 0x0d, 0xc7, 0x0b, + 0x89, 0x7f, 0x64, 0xba, 0xb2, 0xb8, 0xa3, 0x44, 0x8c, 0xb6, 0xa4, 0xa3, 0x77, 0x01, 0x1d, 0x12, + 0x32, 0x36, 0xa8, 0x6b, 0x1b, 0x11, 0x53, 0x3c, 0x42, 0xe7, 0xb0, 0xc2, 0x38, 0x5d, 0xd7, 0xee, + 0x47, 0x74, 0xd4, 0x84, 0x0d, 0x36, 0x7d, 0xe2, 0x85, 0xbe, 0x43, 0x02, 0x63, 0x9f, 0xfa, 0x46, + 0xe0, 0xd2, 0x17, 0xc6, 0x3e, 0x75, 0x5d, 0xfa, 0x82, 0xf8, 0x51, 0xdd, 0xac, 0xea, 0xd2, 0xa1, + 0x2e, 0x40, 0x5b, 0xd4, 0xef, 0xbb, 0xf4, 0xc5, 0x56, 0x84, 0x60, 0x21, 0xdd, 0x6c, 0xce, 0xa1, + 0x63, 0x1d, 0x46, 0x21, 0x5d, 0x4c, 0x1d, 0x38, 0xd6, 0x21, 0x7a, 0x13, 0x96, 0x89, 0x4b, 0x78, + 0xf9, 0x44, 0xa0, 0xf2, 0x1c, 0x55, 0x89, 0x88, 0x0c, 0xa4, 0x7e, 0x04, 0x8a, 0xee, 0x59, 0xfe, + 0x74, 0x9c, 0x58, 0xf3, 0x77, 0x01, 0x31, 0x27, 0x69, 0xb8, 0xd4, 0x3a, 0x34, 0x46, 0xa6, 0x67, + 0x0e, 0xd9, 0xb8, 0xc4, 0xeb, 0x9f, 0xc2, 0x38, 0x3b, 0xd4, 0x3a, 0x7c, 0x2a, 0xe9, 0xea, 0x7b, + 0x00, 0xfd, 0xb1, 0x4f, 0x4c, 0xbb, 0xcb, 0xa2, 0x09, 0x66, 0x3a, 0xde, 0x32, 0x6c, 0xf9, 0xb6, + 0x4a, 0x7d, 0x79, 0xd4, 0x15, 0xc1, 0x68, 0xc5, 0x74, 0xf5, 0x67, 0xe1, 0x7a, 0xcf, 0x35, 0x2d, + 0xfe, 0x9d, 0x41, 0x2f, 0x7e, 0xce, 0x42, 0x8f, 0xa0, 0x20, 0xa0, 0x72, 0x25, 0x53, 0x8f, 0xdb, + 0xac, 0xcf, 0xed, 0x05, 0x2c, 0xf1, 0xcd, 0x0a, 0xc0, 0x4c, 0x8f, 0xfa, 0x67, 0x19, 0x28, 0xc5, + 0xfa, 0x51, 0x4d, 0xbc, 0xd2, 0x84, 0xbe, 0xe9, 0x78, 0x32, 0xe3, 0x2f, 0xe1, 0x24, 0x09, 0xb5, + 0xa1, 0x3c, 0x8e, 0xa5, 0x2f, 0x8d, 0xe7, 0x52, 0x46, 0x8d, 0x93, 0xb2, 0xe8, 0x7d, 0x28, 0x45, + 0x8f, 0xd9, 0x91, 0x87, 0xbd, 0xfc, 0xed, 0x7b, 0x06, 0x57, 0xbf, 0x09, 0xf0, 0x2d, 0xea, 0x78, + 0x03, 0x7a, 0x48, 0x3c, 0xfe, 0xfc, 0xca, 0xf2, 0x45, 0x12, 0x59, 0x51, 0xb6, 0x78, 0x19, 0x40, + 0x2c, 0x41, 0xfc, 0x0a, 0x29, 0x9a, 0xea, 0x5f, 0x65, 0xa1, 0x80, 0x29, 0x0d, 0xb5, 0x06, 0xaa, + 0x41, 0x41, 0xfa, 0x09, 0x7e, 0xff, 0x34, 0x4b, 0x67, 0xa7, 0x9b, 0x79, 0xe1, 0x20, 0xf2, 0x16, + 0xf7, 0x0c, 0x09, 0x0f, 0x9e, 0xbd, 0xc8, 0x83, 0xa3, 0x7b, 0x50, 0x91, 0x20, 0xe3, 0xc0, 0x0c, + 0x0e, 0x44, 0xf2, 0xd6, 0x5c, 0x39, 0x3b, 0xdd, 0x04, 0x81, 0xdc, 0x36, 0x83, 0x03, 0x0c, 0x02, + 0xcd, 0x7e, 0x23, 0x1d, 0xca, 0x1f, 0x53, 0xc7, 0x33, 0x42, 0x3e, 0x09, 0x59, 0x68, 0x4c, 0x5d, + 0xc7, 0xd9, 0x54, 0xe5, 0x97, 0x0a, 0xf0, 0xf1, 0x6c, 0xf2, 0x3a, 0x2c, 0xfb, 0x94, 0x86, 0xc2, + 0x6d, 0x39, 0xd4, 0x93, 0x35, 0x8c, 0x5a, 0x6a, 0x69, 0x9b, 0xd2, 0x10, 0x4b, 0x1c, 0xae, 0xf8, + 0x89, 0x16, 0xba, 0x07, 0x6b, 0xae, 0x19, 0x84, 0x06, 0xf7, 0x77, 0xf6, 0x4c, 0x5b, 0x81, 0x1f, + 0x35, 0xc4, 0x78, 0x5b, 0x9c, 0x15, 0x49, 0xa8, 0xff, 0x94, 0x81, 0x32, 0x9b, 0x8c, 0xb3, 0xef, + 0x58, 0x2c, 0xc8, 0xfb, 0xfc, 0xb1, 0xc7, 0x4d, 0x58, 0xb4, 0x02, 0x5f, 0x1a, 0x95, 0x5f, 0xbe, + 0x5a, 0x1f, 0x63, 0x46, 0x43, 0x1f, 0x41, 0x41, 0xd6, 0x52, 0x44, 0xd8, 0xa1, 0x5e, 0x1d, 0x8e, + 0x4a, 0xdb, 0x48, 0x39, 0xbe, 0x97, 0x67, 0xa3, 0x13, 0x97, 0x00, 0x4e, 0x92, 0xd0, 0x0d, 0xc8, + 0x5a, 0xc2, 0x5c, 0xf2, 0x53, 0x18, 0xad, 0x83, 0xb3, 0x96, 0xa7, 0xfe, 0x20, 0x03, 0xcb, 0xb3, + 0x03, 0xcf, 0x76, 0xc0, 0x2d, 0x28, 0x05, 0x93, 0xbd, 0x60, 0x1a, 0x84, 0x64, 0x14, 0x3d, 0x2d, + 0xc7, 0x04, 0xd4, 0x86, 0x92, 0xe9, 0x0e, 0xa9, 0xef, 0x84, 0x07, 0x23, 0x99, 0xa5, 0xa6, 0x87, + 0x0a, 0x49, 0x9d, 0xf5, 0x46, 0x24, 0x82, 0x67, 0xd2, 0xd1, 0xbd, 0x2f, 0xbe, 0x3f, 0xe0, 0xf7, + 0xfe, 0x1b, 0x50, 0x71, 0xcd, 0x11, 0x2f, 0x2e, 0x85, 0xce, 0x48, 0xcc, 0x23, 0x87, 0xcb, 0x92, + 0x36, 0x70, 0x46, 0x44, 0x55, 0xa1, 0x14, 0x2b, 0x43, 0xab, 0x50, 0x6e, 0xe8, 0x7d, 0xe3, 0xfe, + 0x83, 0x47, 0xc6, 0x63, 0xed, 0xa9, 0xb2, 0x20, 0x63, 0xd3, 0x3f, 0xcf, 0xc0, 0xb2, 0x74, 0x47, + 0x32, 0xde, 0x7f, 0x13, 0x96, 0x7c, 0x73, 0x3f, 0x8c, 0x32, 0x92, 0x9c, 0xd8, 0xd5, 0xcc, 0xc3, + 0xb3, 0x8c, 0x84, 0xb1, 0xd2, 0x33, 0x92, 0xc4, 0xc7, 0x0e, 0x8b, 0x97, 0x7e, 0xec, 0x90, 0xfb, + 0xa9, 0x7c, 0xec, 0xa0, 0xfe, 0x0a, 0xc0, 0x96, 0xe3, 0x92, 0x81, 0xa8, 0x43, 0xa5, 0xe5, 0x97, + 0x2c, 0x86, 0x93, 0x75, 0xce, 0x28, 0x86, 0x6b, 0xb7, 0x30, 0xa3, 0x31, 0xd6, 0xd0, 0xb1, 0xe5, + 0x61, 0xe4, 0xac, 0xc7, 0x8c, 0x35, 0x74, 0xec, 0xf8, 0x55, 0x2e, 0x77, 0xd5, 0xab, 0xdc, 0x49, + 0x06, 0x56, 0x65, 0xec, 0x1a, 0xbb, 0xdf, 0x2f, 0x43, 0x49, 0x84, 0xb1, 0xb3, 0x84, 0x8e, 0x3f, + 0xf0, 0x0b, 0x5c, 0xbb, 0x85, 0x8b, 0x82, 0xdd, 0xb6, 0xd1, 0x26, 0x94, 0x25, 0x34, 0xf1, 0xd9, + 0x14, 0x08, 0x52, 0x87, 0x0d, 0xff, 0xab, 0x90, 0xdb, 0x77, 0x5c, 0x22, 0x37, 0x7a, 0xaa, 0x03, + 0x98, 0x19, 0x60, 0x7b, 0x01, 0x73, 0x74, 0xb3, 0x18, 0x15, 0xea, 0xf8, 0xf8, 0x64, 0xda, 0x99, + 0x1c, 0x9f, 0xc8, 0x40, 0xe7, 0xc6, 0x27, 0x70, 0x6c, 0x7c, 0x82, 0x2d, 0xc6, 0x27, 0xa1, 0xc9, + 0xf1, 0x09, 0xd2, 0x4f, 0x65, 0x7c, 0x3b, 0x70, 0xa3, 0xe9, 0x9a, 0xd6, 0xa1, 0xeb, 0x04, 0x21, + 0xb1, 0x93, 0x1e, 0xe3, 0x01, 0x14, 0xce, 0x05, 0x9d, 0x97, 0x55, 0x34, 0x25, 0x52, 0xfd, 0xf7, + 0x0c, 0x54, 0xb6, 0x89, 0xe9, 0x86, 0x07, 0xb3, 0xb2, 0x51, 0x48, 0x82, 0x50, 0x5e, 0x56, 0xfc, + 0x37, 0xfa, 0x1a, 0x14, 0xe3, 0x98, 0xe4, 0xca, 0xb7, 0xb9, 0x18, 0x8a, 0x1e, 0xc2, 0x12, 0x3b, + 0x63, 0x74, 0x12, 0x25, 0x3b, 0x97, 0x3d, 0xfb, 0x48, 0x24, 0xbb, 0x64, 0x7c, 0xc2, 0x83, 0x10, + 0xbe, 0x95, 0xf2, 0x38, 0x6a, 0xa2, 0xff, 0x0f, 0x15, 0xfe, 0x6a, 0x11, 0xc5, 0x5c, 0xf9, 0xab, + 0x74, 0x96, 0xc5, 0xc3, 0xa3, 0x88, 0xb7, 0xfe, 0x38, 0x0b, 0x6b, 0x4f, 0xcd, 0xe9, 0x1e, 0x91, + 0x6e, 0x83, 0xd8, 0x98, 0x58, 0xd4, 0xb7, 0x51, 0x2f, 0xe9, 0x6e, 0x2e, 0x79, 0xc7, 0x4c, 0x13, + 0x4e, 0xf7, 0x3a, 0x51, 0x02, 0x96, 0x4d, 0x24, 0x60, 0x6b, 0x90, 0xf7, 0xa8, 0x67, 0x11, 0xe9, + 0x8b, 0x44, 0x43, 0xfd, 0xed, 0x4c, 0xd2, 0xd7, 0x54, 0xe3, 0x37, 0x46, 0x5e, 0x81, 0xea, 0xd0, + 0x30, 0xee, 0x0e, 0x7d, 0x04, 0xd5, 0xbe, 0xae, 0x61, 0x7d, 0xd0, 0xec, 0x7e, 0xdb, 0xe8, 0x37, + 0x76, 0xfa, 0x8d, 0x07, 0xf7, 0x8c, 0x5e, 0x77, 0xe7, 0x3b, 0xf7, 0x1f, 0xde, 0xfb, 0x9a, 0x92, + 0xa9, 0xd6, 0x8e, 0x4f, 0x6a, 0xb7, 0x3a, 0x0d, 0x6d, 0x47, 0x1c, 0x99, 0x3d, 0xfa, 0xb2, 0x6f, + 0xba, 0x81, 0xf9, 0xe0, 0x5e, 0x8f, 0xba, 0x53, 0x86, 0x41, 0x5f, 0x01, 0xb4, 0xa5, 0xe3, 0x8e, + 0x3e, 0x30, 0x22, 0x87, 0xa6, 0x35, 0x35, 0x25, 0x2b, 0xd2, 0x9a, 0x2d, 0xe2, 0x7b, 0x24, 0x6c, + 0xe8, 0xfd, 0xfb, 0x0f, 0x1e, 0x69, 0x4d, 0x8d, 0x1d, 0x82, 0x4a, 0xf2, 0x76, 0x4b, 0x5e, 0xda, + 0x99, 0x0b, 0x2f, 0xed, 0xd9, 0xdd, 0x9f, 0xbd, 0xe0, 0xee, 0xdf, 0x82, 0x35, 0xcb, 0xa7, 0x41, + 0x60, 0xb0, 0x5c, 0x81, 0xd8, 0x73, 0xd9, 0xc8, 0x17, 0xce, 0x4e, 0x37, 0xaf, 0x69, 0x8c, 0xdf, + 0xe7, 0x6c, 0xa9, 0xfe, 0x9a, 0x95, 0x20, 0xf1, 0x9e, 0xd4, 0xdf, 0x5f, 0x64, 0x61, 0x97, 0x73, + 0xe4, 0xb8, 0x64, 0x48, 0x02, 0xf4, 0x0c, 0x56, 0x2d, 0x9f, 0xd8, 0x2c, 0x09, 0x30, 0xdd, 0xe4, + 0xc7, 0xba, 0xff, 0x2f, 0x35, 0x02, 0x8a, 0x05, 0xeb, 0x5a, 0x2c, 0xd5, 0x1f, 0x13, 0x0b, 0xaf, + 0x58, 0xe7, 0xda, 0xe8, 0x63, 0x58, 0x0d, 0x88, 0xeb, 0x78, 0x93, 0x97, 0x86, 0x45, 0xbd, 0x90, + 0xbc, 0x8c, 0xde, 0xd6, 0xae, 0xd2, 0xdb, 0xd7, 0x77, 0x98, 0x94, 0x26, 0x84, 0x9a, 0xe8, 0xec, + 0x74, 0x73, 0xe5, 0x3c, 0x0d, 0xaf, 0x48, 0xcd, 0xb2, 0x5d, 0xed, 0xc0, 0xca, 0xf9, 0xd1, 0xa0, + 0x35, 0xe9, 0x29, 0xb8, 0xc3, 0x89, 0x3c, 0x01, 0xba, 0x05, 0x45, 0x9f, 0x0c, 0x9d, 0x20, 0xf4, + 0x85, 0x99, 0x19, 0x27, 0xa6, 0x30, 0x3f, 0x21, 0xbe, 0xa5, 0xaa, 0xfe, 0x12, 0xcc, 0xf5, 0xc8, + 0x8e, 0x96, 0xed, 0x04, 0xe6, 0x9e, 0x54, 0x59, 0xc4, 0x51, 0x93, 0xed, 0xd8, 0x49, 0x10, 0x87, + 0x75, 0xfc, 0x37, 0xa3, 0xf1, 0xf8, 0x43, 0x7e, 0x59, 0xc6, 0x23, 0x8c, 0xe8, 0x03, 0xd6, 0x5c, + 0xe2, 0x03, 0xd6, 0x35, 0xc8, 0xbb, 0xe4, 0x88, 0xb8, 0xe2, 0xe6, 0xc7, 0xa2, 0x71, 0xe7, 0x1e, + 0x54, 0xa2, 0x2f, 0x25, 0xf9, 0x37, 0x18, 0x45, 0xc8, 0x0d, 0x1a, 0xfd, 0x27, 0xca, 0x02, 0x02, + 0x28, 0x88, 0x9d, 0x2c, 0xde, 0xfd, 0xb4, 0x6e, 0x67, 0xab, 0xfd, 0x58, 0xc9, 0xde, 0xf9, 0x9d, + 0x1c, 0x94, 0xe2, 0x97, 0x27, 0x76, 0xd3, 0x74, 0xf4, 0xe7, 0xd1, 0x51, 0x88, 0xe9, 0x1d, 0xf2, + 0x02, 0xbd, 0x31, 0xab, 0x59, 0x7d, 0x24, 0x9e, 0xda, 0x63, 0x76, 0x54, 0xaf, 0x7a, 0x0b, 0x8a, + 0x8d, 0x7e, 0xbf, 0xfd, 0xb8, 0xa3, 0xb7, 0x94, 0x4f, 0x33, 0xd5, 0x2f, 0x1c, 0x9f, 0xd4, 0xae, + 0xc5, 0xa0, 0x46, 0x20, 0x36, 0x1f, 0x47, 0x69, 0x9a, 0xde, 0x1b, 0xe8, 0x2d, 0xe5, 0x93, 0xec, + 0x3c, 0x8a, 0xd7, 0x60, 0xf8, 0x47, 0x40, 0xa5, 0x1e, 0xd6, 0x7b, 0x0d, 0xcc, 0x3a, 0xfc, 0x34, + 0x2b, 0x4a, 0x69, 0xb3, 0x1e, 0x7d, 0x32, 0x36, 0x7d, 0xd6, 0xe7, 0x46, 0xf4, 0x55, 0xdd, 0x27, + 0x8b, 0xe2, 0x43, 0x91, 0xd9, 0x33, 0x1a, 0x31, 0xed, 0x29, 0xeb, 0x8d, 0xbf, 0x5f, 0x72, 0x35, + 0x8b, 0x73, 0xbd, 0xf5, 0x99, 0xa7, 0x62, 0x5a, 0x54, 0x58, 0xc2, 0xbb, 0x9d, 0x0e, 0x03, 0x7d, + 0x92, 0x9b, 0x9b, 0x1d, 0x9e, 0x78, 0x2c, 0xbf, 0x46, 0xb7, 0xa1, 0x18, 0x3d, 0x6f, 0x2a, 0x9f, + 0xe6, 0xe6, 0x06, 0xa4, 0x45, 0x6f, 0xb3, 0xbc, 0xc3, 0xed, 0xdd, 0x01, 0xff, 0xe8, 0xef, 0x93, + 0xfc, 0x7c, 0x87, 0x07, 0x93, 0xd0, 0xa6, 0x2f, 0x3c, 0x76, 0x66, 0x65, 0xd5, 0xee, 0xd3, 0xbc, + 0xf0, 0x05, 0x31, 0x46, 0x96, 0xec, 0xde, 0x82, 0x22, 0xd6, 0xbf, 0x25, 0xbe, 0x0f, 0xfc, 0xa4, + 0x30, 0xa7, 0x07, 0x93, 0x8f, 0x89, 0xc5, 0x7a, 0xab, 0x41, 0x01, 0xeb, 0x4f, 0xbb, 0xcf, 0x74, + 0xe5, 0x0f, 0x0a, 0x73, 0x7a, 0x30, 0x19, 0x51, 0xfe, 0x95, 0x54, 0xb1, 0x8b, 0x7b, 0xdb, 0x0d, + 0xbe, 0x28, 0xf3, 0x7a, 0xba, 0xfe, 0xf8, 0xc0, 0xf4, 0x88, 0x3d, 0xfb, 0x9e, 0x26, 0x66, 0xdd, + 0xf9, 0x39, 0x28, 0x46, 0x91, 0x2e, 0xda, 0x80, 0xc2, 0xf3, 0x2e, 0x7e, 0xa2, 0x63, 0x65, 0x41, + 0x58, 0x39, 0xe2, 0x3c, 0x17, 0x39, 0x4a, 0x0d, 0x96, 0x9e, 0x36, 0x3a, 0x8d, 0xc7, 0x3a, 0x8e, + 0x4a, 0xee, 0x11, 0x40, 0x86, 0x6b, 0x55, 0x45, 0x76, 0x10, 0xeb, 0x6c, 0xae, 0x7f, 0xff, 0x47, + 0x1b, 0x0b, 0x3f, 0xfc, 0xd1, 0xc6, 0xc2, 0x27, 0x67, 0x1b, 0x99, 0xef, 0x9f, 0x6d, 0x64, 0xfe, + 0xfe, 0x6c, 0x23, 0xf3, 0x6f, 0x67, 0x1b, 0x99, 0xbd, 0x02, 0xbf, 0x54, 0x1e, 0xfe, 0x6f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x80, 0x94, 0x1b, 0x9c, 0x7a, 0x32, 0x00, 0x00, } diff --git a/api/types.proto b/api/types.proto index a7c58264be..dc512b503f 100644 --- a/api/types.proto +++ b/api/types.proto @@ -125,6 +125,9 @@ message NodeDescription { // Information on the node's TLS setup NodeTLSInfo tls_info = 5 [(gogoproto.customname) = "TLSInfo"]; + + // FIPS indicates whether the node has FIPS-enabled + bool fips = 6 [(gogoproto.customname) = "FIPS"]; } message NodeTLSInfo { @@ -1035,6 +1038,7 @@ message MaybeEncryptedRecord { enum Algorithm { NONE = 0 [(gogoproto.enumvalue_customname) = "NotEncrypted"]; SECRETBOX_SALSA20_POLY1305 = 1 [(gogoproto.enumvalue_customname) = "NACLSecretboxSalsa20Poly1305"]; + FERNET_AES_128_CBC = 2 [(gogoproto.enumvalue_customname) = "FernetAES128CBC"]; } Algorithm algorithm = 1; diff --git a/ca/certificates.go b/ca/certificates.go index 54c3b296f9..f2d3dbac55 100644 --- a/ca/certificates.go +++ b/ca/certificates.go @@ -26,7 +26,6 @@ import ( "github.com/cloudflare/cfssl/signer/local" "github.com/docker/go-events" "github.com/docker/swarmkit/api" - "github.com/docker/swarmkit/ca/keyutils" "github.com/docker/swarmkit/ca/pkcs8" "github.com/docker/swarmkit/connectionbroker" "github.com/docker/swarmkit/ioutils" @@ -51,13 +50,6 @@ const ( RootKeySize = 256 // RootKeyAlgo defines the default algorithm for the root CA Key RootKeyAlgo = "ecdsa" - // PassphraseENVVar defines the environment variable to look for the - // root CA private key material encryption key - PassphraseENVVar = "SWARM_ROOT_CA_PASSPHRASE" - // PassphraseENVVarPrev defines the alternate environment variable to look for the - // root CA private key material encryption key. It can be used for seamless - // KEK rotations. - PassphraseENVVarPrev = "SWARM_ROOT_CA_PASSPHRASE_PREV" // RootCAExpiration represents the default expiration for the root CA in seconds (20 years) RootCAExpiration = "630720000s" // DefaultNodeCertExpiration represents the default expiration for node certificates (3 months) @@ -641,28 +633,10 @@ func newLocalSigner(keyBytes, certBytes []byte, certExpiry time.Duration, rootPo return nil, errors.Wrap(err, "error while validating signing CA certificate against roots and intermediates") } - var ( - passphraseStr string - passphrase, passphrasePrev []byte - priv crypto.Signer - ) - - // Attempt two distinct passphrases, so we can do a hitless passphrase rotation - if passphraseStr = os.Getenv(PassphraseENVVar); passphraseStr != "" { - passphrase = []byte(passphraseStr) - } - - if p := os.Getenv(PassphraseENVVarPrev); p != "" { - passphrasePrev = []byte(p) - } - - // Attempt to decrypt the current private-key with the passphrases provided - priv, err = keyutils.ParsePrivateKeyPEMWithPassword(keyBytes, passphrase) + // The key should not be encrypted, but it could be in PKCS8 format rather than PKCS1 + priv, err := helpers.ParsePrivateKeyPEM(keyBytes) if err != nil { - priv, err = keyutils.ParsePrivateKeyPEMWithPassword(keyBytes, passphrasePrev) - if err != nil { - return nil, errors.Wrap(err, "malformed private key") - } + return nil, errors.Wrap(err, "malformed private key") } // We will always use the first certificate inside of the root bundle as the active one @@ -675,17 +649,6 @@ func newLocalSigner(keyBytes, certBytes []byte, certExpiry time.Duration, rootPo return nil, err } - // If the key was loaded from disk unencrypted, but there is a passphrase set, - // ensure it is encrypted, so it doesn't hit raft in plain-text - // we don't have to check for nil, because if we couldn't pem-decode the bytes, then parsing above would have failed - keyBlock, _ := pem.Decode(keyBytes) - if passphraseStr != "" && !keyutils.IsEncryptedPEMBlock(keyBlock) { - keyBytes, err = EncryptECPrivateKey(keyBytes, passphraseStr) - if err != nil { - return nil, errors.Wrap(err, "unable to encrypt signing CA key material") - } - } - return &LocalSigner{Cert: certBytes, Key: keyBytes, Signer: signer, parsedCert: parsedCerts[0], cryptoSigner: priv}, nil } @@ -817,14 +780,6 @@ func CreateRootCA(rootCN string) (RootCA, error) { return RootCA{}, err } - // Convert key to PKCS#8 in FIPS mode - if keyutils.FIPSEnabled() { - key, err = pkcs8.ConvertECPrivateKeyPEM(key) - if err != nil { - return RootCA{}, err - } - } - rootCA, err := NewRootCA(cert, cert, key, DefaultNodeCertExpiration, nil) if err != nil { return RootCA{}, err @@ -976,30 +931,6 @@ func GenerateNewCSR() ([]byte, []byte, error) { return csr, key, err } -// EncryptECPrivateKey receives a PEM encoded private key and returns an encrypted -// AES256 version using a passphrase -// TODO: Make this method generic to handle RSA keys -func EncryptECPrivateKey(key []byte, passphraseStr string) ([]byte, error) { - passphrase := []byte(passphraseStr) - - keyBlock, _ := pem.Decode(key) - if keyBlock == nil { - // This RootCA does not have a valid signer. - return nil, errors.New("error while decoding PEM key") - } - - encryptedPEMBlock, err := keyutils.EncryptPEMBlock(keyBlock.Bytes, passphrase) - if err != nil { - return nil, err - } - - if encryptedPEMBlock.Headers == nil { - return nil, errors.New("unable to encrypt key - invalid PEM file produced") - } - - return pem.EncodeToMemory(encryptedPEMBlock), nil -} - // NormalizePEMs takes a bundle of PEM-encoded certificates in a certificate bundle, // decodes them, removes headers, and re-encodes them to make sure that they have // consistent whitespace. Note that this is intended to normalize x509 certificates diff --git a/ca/certificates_test.go b/ca/certificates_test.go index f43a192331..2c7895510c 100644 --- a/ca/certificates_test.go +++ b/ca/certificates_test.go @@ -27,8 +27,6 @@ import ( "github.com/docker/go-events" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" - "github.com/docker/swarmkit/ca/keyutils" - "github.com/docker/swarmkit/ca/pkcs8" cautils "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/connectionbroker" "github.com/docker/swarmkit/identity" @@ -44,9 +42,6 @@ import ( ) func init() { - os.Setenv(ca.PassphraseENVVar, "") - os.Setenv(ca.PassphraseENVVarPrev, "") - ca.RenewTLSExponentialBackoff = events.ExponentialBackoffConfig{ Base: 250 * time.Millisecond, Factor: 250 * time.Millisecond, @@ -82,31 +77,6 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func TestCreateRootCAKeyFormat(t *testing.T) { - // Check if the CA key generated is PKCS#1 when FIPS-mode is off - rootCA, err := ca.CreateRootCA("rootCA") - require.NoError(t, err) - - s, err := rootCA.Signer() - require.NoError(t, err) - block, _ := pem.Decode(s.Key) - require.NotNil(t, block) - require.Equal(t, "EC PRIVATE KEY", block.Type) - - // Check if the CA key generated is PKCS#8 when FIPS-mode is on - os.Setenv(keyutils.FIPSEnvVar, "1") - defer os.Unsetenv(keyutils.FIPSEnvVar) - - rootCA, err = ca.CreateRootCA("rootCA") - require.NoError(t, err) - - s, err = rootCA.Signer() - require.NoError(t, err) - block, _ = pem.Decode(s.Key) - require.NotNil(t, block) - require.Equal(t, "PRIVATE KEY", block.Type) -} - func TestCreateRootCASaveRootCA(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") assert.NoError(t, err) @@ -231,21 +201,6 @@ some random garbage\n require.Error(t, err) } -func TestEncryptECPrivateKey(t *testing.T) { - tempBaseDir, err := ioutil.TempDir("", "swarm-ca-test-") - assert.NoError(t, err) - defer os.RemoveAll(tempBaseDir) - - _, key, err := ca.GenerateNewCSR() - assert.NoError(t, err) - encryptedKey, err := ca.EncryptECPrivateKey(key, "passphrase") - assert.NoError(t, err) - - keyBlock, _ := pem.Decode(encryptedKey) - assert.NotNil(t, keyBlock) - assert.True(t, pkcs8.IsEncryptedPEMBlock(keyBlock)) -} - func TestParseValidateAndSignCSR(t *testing.T) { rootCA, err := ca.CreateRootCA("rootCN") assert.NoError(t, err) @@ -1331,65 +1286,6 @@ func TestRootCAWithCrossSignedIntermediates(t *testing.T) { checkValidateAgainstAllRoots(tlsCert) } -func TestNewRootCAWithPassphrase(t *testing.T) { - defer os.Setenv(ca.PassphraseENVVar, "") - defer os.Setenv(ca.PassphraseENVVarPrev, "") - - rootCA, err := ca.CreateRootCA("rootCN") - assert.NoError(t, err) - rcaSigner, err := rootCA.Signer() - assert.NoError(t, err) - - // Ensure that we're encrypting the Key bytes out of NewRoot if there - // is a passphrase set as an env Var - os.Setenv(ca.PassphraseENVVar, "password1") - newRootCA, err := ca.NewRootCA(rootCA.Certs, rcaSigner.Cert, rcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.NoError(t, err) - nrcaSigner, err := newRootCA.Signer() - assert.NoError(t, err) - assert.NotEqual(t, rcaSigner.Key, nrcaSigner.Key) - assert.Equal(t, rootCA.Certs, newRootCA.Certs) - assert.NotContains(t, string(rcaSigner.Key), string(nrcaSigner.Key)) - keyBlock, _ := pem.Decode(nrcaSigner.Key) - assert.NotNil(t, keyBlock) - assert.True(t, keyutils.IsEncryptedPEMBlock(keyBlock)) - - // Ensure that we're decrypting the Key bytes out of NewRoot if there - // is a passphrase set as an env Var - anotherNewRootCA, err := ca.NewRootCA(newRootCA.Certs, nrcaSigner.Cert, nrcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.NoError(t, err) - anrcaSigner, err := anotherNewRootCA.Signer() - assert.NoError(t, err) - assert.Equal(t, newRootCA, anotherNewRootCA) - assert.NotContains(t, string(rcaSigner.Key), string(anrcaSigner.Key)) - keyBlock, _ = pem.Decode(anrcaSigner.Key) - assert.NotNil(t, keyBlock) - assert.True(t, keyutils.IsEncryptedPEMBlock(keyBlock)) - - // Ensure that we cant decrypt the Key bytes out of NewRoot if there - // is a wrong passphrase set as an env Var - os.Setenv(ca.PassphraseENVVar, "password2") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Certs, nrcaSigner.Cert, nrcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.Error(t, err) - - // Ensure that we cant decrypt the Key bytes out of NewRoot if there - // is a wrong passphrase set as an env Var - os.Setenv(ca.PassphraseENVVarPrev, "password2") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Certs, nrcaSigner.Cert, nrcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.Error(t, err) - - // Ensure that we can decrypt the Key bytes out of NewRoot if there - // is a wrong passphrase set as an env Var, but a valid as Prev - os.Setenv(ca.PassphraseENVVarPrev, "password1") - anotherNewRootCA, err = ca.NewRootCA(newRootCA.Certs, nrcaSigner.Cert, nrcaSigner.Key, ca.DefaultNodeCertExpiration, nil) - assert.NoError(t, err) - assert.Equal(t, newRootCA, anotherNewRootCA) - assert.NotContains(t, string(rcaSigner.Key), string(anrcaSigner.Key)) - keyBlock, _ = pem.Decode(anrcaSigner.Key) - assert.NotNil(t, keyBlock) - assert.True(t, keyutils.IsEncryptedPEMBlock(keyBlock)) -} - type certTestCase struct { cert []byte errorStr string diff --git a/ca/config.go b/ca/config.go index 0fd4ebe0f0..4a7230ac2f 100644 --- a/ca/config.go +++ b/ca/config.go @@ -55,6 +55,11 @@ var ( // GetCertRetryInterval is how long to wait before retrying a node // certificate or root certificate request. GetCertRetryInterval = 2 * time.Second + + // errInvalidJoinToken is returned when attempting to parse an invalid join + // token (e.g. when attempting to get the version, fipsness, or the root ca + // digest) + errInvalidJoinToken = errors.New("invalid join token") ) // SecurityConfig is used to represent a node's security configuration. It includes information about @@ -87,6 +92,81 @@ type CertificateUpdate struct { Err error } +// ParsedJoinToken is the data from a join token, once parsed +type ParsedJoinToken struct { + // Version is the version of the join token that is being parsed + Version int + + // RootDigest is the digest of the root CA certificate of the cluster, which + // is always part of the join token so that the root CA can be verified + // upon initial node join + RootDigest digest.Digest + + // Secret is the randomly-generated secret part of the join token - when + // rotating a join token, this is the value that is changed unless some other + // property of the cluster (like the root CA) is changed. + Secret string + + // FIPS indicates whether the join token specifies that the cluster mandates + // that all nodes must have FIPS mode enabled. + FIPS bool +} + +// ParseJoinToken parses a join token. Current format is v2, but this is currently used only if the cluster requires +// mandatory FIPS, in order to facilitate mixed version clusters. +// v1: SWMTKN-1--<16-byte secret in base 36 0-left-padded to 25 chars> +// v2: SWMTKN-2-<0/1 whether its FIPS or not>- +func ParseJoinToken(token string) (*ParsedJoinToken, error) { + split := strings.Split(token, "-") + numParts := len(split) + + // v1 has 4, v2 has 5 + if numParts < 4 || split[0] != "SWMTKN" { + return nil, errInvalidJoinToken + } + + var ( + version int + fips bool + ) + + switch split[1] { + case "1": + if numParts != 4 { + return nil, errInvalidJoinToken + } + version = 1 + case "2": + if numParts != 5 || (split[2] != "0" && split[2] != "1") { + return nil, errInvalidJoinToken + } + version = 2 + fips = split[2] == "1" + default: + return nil, errInvalidJoinToken + } + + secret := split[numParts-1] + rootDigest := split[numParts-2] + if len(rootDigest) != base36DigestLen || len(secret) != maxGeneratedSecretLength { + return nil, errInvalidJoinToken + } + + var digestInt big.Int + digestInt.SetString(rootDigest, joinTokenBase) + + d, err := digest.Parse(fmt.Sprintf("sha256:%0[1]*s", 64, digestInt.Text(16))) + if err != nil { + return nil, err + } + return &ParsedJoinToken{ + Version: version, + RootDigest: d, + Secret: secret, + FIPS: fips, + }, nil +} + func validateRootCAAndTLSCert(rootCA *RootCA, tlsKeyPair *tls.Certificate) error { var ( leafCert *x509.Certificate @@ -275,8 +355,14 @@ func NewConfigPaths(baseCertDir string) *SecurityConfigPaths { } } -// GenerateJoinToken creates a new join token. -func GenerateJoinToken(rootCA *RootCA) string { +// GenerateJoinToken creates a new join token. Current format is v2, but this is +// currently used only if the cluster requires mandatory FIPS, in order to +// facilitate mixed version clusters (the `fips` parameter is set to true). +// Otherwise, v1 is used so as to maintain compatibility in mixed version +// non-FIPS clusters. +// v1: SWMTKN-1--<16-byte secret in base 36 0-left-padded to 25 chars> +// v2: SWMTKN-2-<0/1 whether its FIPS or not>- +func GenerateJoinToken(rootCA *RootCA, fips bool) string { var secretBytes [generatedSecretEntropyBytes]byte if _, err := cryptorand.Read(secretBytes[:]); err != nil { @@ -286,19 +372,13 @@ func GenerateJoinToken(rootCA *RootCA) string { var nn, digest big.Int nn.SetBytes(secretBytes[:]) digest.SetString(rootCA.Digest.Hex(), 16) - return fmt.Sprintf("SWMTKN-1-%0[1]*s-%0[3]*s", base36DigestLen, digest.Text(joinTokenBase), maxGeneratedSecretLength, nn.Text(joinTokenBase)) -} -func getCAHashFromToken(token string) (digest.Digest, error) { - split := strings.Split(token, "-") - if len(split) != 4 || split[0] != "SWMTKN" || split[1] != "1" || len(split[2]) != base36DigestLen || len(split[3]) != maxGeneratedSecretLength { - return "", errors.New("invalid join token") + fmtString := "SWMTKN-1-%0[1]*s-%0[3]*s" + if fips { + fmtString = "SWMTKN-2-1-%0[1]*s-%0[3]*s" } - - var digestInt big.Int - digestInt.SetString(split[2], joinTokenBase) - - return digest.Parse(fmt.Sprintf("sha256:%0[1]*s", 64, digestInt.Text(16))) + return fmt.Sprintf(fmtString, base36DigestLen, + digest.Text(joinTokenBase), maxGeneratedSecretLength, nn.Text(joinTokenBase)) } // DownloadRootCA tries to retrieve a remote root CA and matches the digest against the provided token. @@ -312,10 +392,11 @@ func DownloadRootCA(ctx context.Context, paths CertPaths, token string, connBrok err error ) if token != "" { - d, err = getCAHashFromToken(token) + parsed, err := ParseJoinToken(token) if err != nil { return RootCA{}, err } + d = parsed.RootDigest } // Get the remote CA certificate, verify integrity with the // hash provided. Retry up to 5 times, in case the manager we @@ -414,6 +495,10 @@ type CertificateRequestConfig struct { NodeCertificateStatusRequestTimeout time.Duration // RetryInterval specifies how long to delay between retries, if non-zero. RetryInterval time.Duration + // Organization is the organization to use for a TLS certificate when creating + // a security config from scratch. If not provided, a random ID is generated. + // For swarm certificates, the organization is the cluster ID. + Organization string } // CreateSecurityConfig creates a new key and cert for this node, either locally @@ -423,7 +508,10 @@ func (rootCA RootCA) CreateSecurityConfig(ctx context.Context, krw *KeyReadWrite // Create a new random ID for this certificate cn := identity.NewID() - org := identity.NewID() + org := config.Organization + if config.Organization == "" { + org = identity.NewID() + } proposedRole := ManagerRole tlsKeyPair, issuerInfo, err := rootCA.IssueAndSaveNewCertificates(krw, cn, proposedRole, org) diff --git a/ca/config_test.go b/ca/config_test.go index 7b9bdab53c..f30d7e3284 100644 --- a/ca/config_test.go +++ b/ca/config_test.go @@ -33,13 +33,33 @@ import ( ) func TestDownloadRootCASuccess(t *testing.T) { - tc := cautils.NewTestCA(t) + for _, fips := range []bool{true, false} { + testDownloadRootCASuccess(t, fips) + } +} +func testDownloadRootCASuccess(t *testing.T, fips bool) { + var tc *cautils.TestCA + if fips { + tc = cautils.NewFIPSTestCA(t) + } else { + tc = cautils.NewTestCA(t) + } defer tc.Stop() + token := ca.GenerateJoinToken(&tc.RootCA, fips) + + // if we require mandatory FIPS, the join token uses a new format. otherwise + // the join token should use the old format. + prefix := "SWMTKN-1-" + if fips { + prefix = "SWMTKN-2-1-" + } + require.True(t, strings.HasPrefix(token, prefix)) + // Remove the CA cert os.RemoveAll(tc.Paths.RootCA.Cert) - rootCA, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, tc.WorkerToken, tc.ConnBroker) + rootCA, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, token, tc.ConnBroker) require.NoError(t, err) require.NotNil(t, rootCA.Pool) require.NotNil(t, rootCA.Certs) @@ -70,23 +90,24 @@ func TestDownloadRootCAWrongCAHash(t *testing.T) { // invalid token for _, invalid := range []string{ "invalidtoken", // completely invalid - "SWMTKN-1-3wkodtpeoipd1u1hi0ykdcdwhw16dk73ulqqtn14b3indz68rf-4myj5xihyto11dg1cn55w8p6", // mistyped + "SWMTKN-1-3wkodtpeoipd1u1hi0ykdcdwhw16dk73ulqqtn14b3indz68rf-4myj5xihyto11dg1cn55w8p6", // mistyped + "SWMTKN-2-1fhvpatk6ms36i3uc64tsv1ybyuxkb899zbjpq4ib64qwbibz4-1g3as27iwmko5yqh1byv868hx", // version 2 should have 5 tokens + "SWMTKN-0-1fhvpatk6ms36i3uc64tsv1ybyuxkb899zbjpq4ib64qwbibz4-1g3as27iwmko5yqh1byv868hx", // invalid version } { _, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, invalid, tc.ConnBroker) require.Error(t, err) require.Contains(t, err.Error(), "invalid join token") } - // invalid hash token - splitToken := strings.Split(tc.ManagerToken, "-") - splitToken[2] = "1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e" - replacementToken := strings.Join(splitToken, "-") - - os.RemoveAll(tc.Paths.RootCA.Cert) - - _, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, replacementToken, tc.ConnBroker) - require.Error(t, err) - require.Contains(t, err.Error(), "remote CA does not match fingerprint.") + // invalid hash token - can get the wrong hash from both version 1 and version 2 + for _, wrongToken := range []string{ + "SWMTKN-1-1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e-4myj5xihyto11dg1cn55w8p61", + "SWMTKN-2-0-1kxftv4ofnc6mt30lmgipg6ngf9luhwqopfk1tz6bdmnkubg0e-4myj5xihyto11dg1cn55w8p61", + } { + _, err := ca.DownloadRootCA(tc.Context, tc.Paths.RootCA, wrongToken, tc.ConnBroker) + require.Error(t, err) + require.Contains(t, err.Error(), "remote CA does not match fingerprint.") + } } func TestCreateSecurityConfigEmptyDir(t *testing.T) { @@ -98,27 +119,36 @@ func TestCreateSecurityConfigEmptyDir(t *testing.T) { assert.NoError(t, tc.CAServer.Stop()) // Remove all the contents from the temp dir and try again with a new node - os.RemoveAll(tc.TempDir) - krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) - nodeConfig, cancel, err := tc.RootCA.CreateSecurityConfig(tc.Context, krw, - ca.CertificateRequestConfig{ - Token: tc.WorkerToken, - ConnBroker: tc.ConnBroker, - }) - assert.NoError(t, err) - cancel() - assert.NotNil(t, nodeConfig) - assert.NotNil(t, nodeConfig.ClientTLSCreds) - assert.NotNil(t, nodeConfig.ServerTLSCreds) - assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) + for _, org := range []string{ + "", + "my_org", + } { + os.RemoveAll(tc.TempDir) + krw := ca.NewKeyReadWriter(tc.Paths.Node, nil, nil) + nodeConfig, cancel, err := tc.RootCA.CreateSecurityConfig(tc.Context, krw, + ca.CertificateRequestConfig{ + Token: tc.WorkerToken, + ConnBroker: tc.ConnBroker, + Organization: org, + }) + assert.NoError(t, err) + cancel() + assert.NotNil(t, nodeConfig) + assert.NotNil(t, nodeConfig.ClientTLSCreds) + assert.NotNil(t, nodeConfig.ServerTLSCreds) + assert.Equal(t, tc.RootCA, *nodeConfig.RootCA()) + if org != "" { + assert.Equal(t, org, nodeConfig.ClientTLSCreds.Organization()) + } - root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) - assert.NoError(t, err) + root, err := helpers.ParseCertificatePEM(tc.RootCA.Certs) + assert.NoError(t, err) - issuerInfo := nodeConfig.IssuerInfo() - assert.NotNil(t, issuerInfo) - assert.Equal(t, root.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) - assert.Equal(t, root.RawSubject, issuerInfo.Subject) + issuerInfo := nodeConfig.IssuerInfo() + assert.NotNil(t, issuerInfo) + assert.Equal(t, root.RawSubjectPublicKeyInfo, issuerInfo.PublicKey) + assert.Equal(t, root.RawSubject, issuerInfo.Subject) + } } func TestCreateSecurityConfigNoCerts(t *testing.T) { diff --git a/ca/keyreadwriter.go b/ca/keyreadwriter.go index c929523f38..cf4517fff4 100644 --- a/ca/keyreadwriter.go +++ b/ca/keyreadwriter.go @@ -73,21 +73,30 @@ func (e ErrInvalidKEK) Error() string { // KeyReadWriter is an object that knows how to read and write TLS keys and certs to disk, // optionally encrypted and optionally updating PEM headers. type KeyReadWriter struct { - mu sync.Mutex - kekData KEKData - paths CertPaths - headersObj PEMKeyHeaders + mu sync.Mutex + kekData KEKData + paths CertPaths + headersObj PEMKeyHeaders + keyFormatter keyutils.Formatter } // NewKeyReadWriter creates a new KeyReadWriter func NewKeyReadWriter(paths CertPaths, kek []byte, headersObj PEMKeyHeaders) *KeyReadWriter { return &KeyReadWriter{ - kekData: KEKData{KEK: kek}, - paths: paths, - headersObj: headersObj, + kekData: KEKData{KEK: kek}, + paths: paths, + headersObj: headersObj, + keyFormatter: keyutils.Default, } } +// SetKeyFormatter sets the keyformatter with which to encrypt and decrypt keys +func (k *KeyReadWriter) SetKeyFormatter(kf keyutils.Formatter) { + k.mu.Lock() + defer k.mu.Unlock() + k.keyFormatter = kf +} + // Migrate checks to see if a temporary key file exists. Older versions of // swarmkit wrote temporary keys instead of temporary certificates, so // migrate that temporary key if it exists. We want to write temporary certificates, @@ -324,8 +333,10 @@ func (k *KeyReadWriter) readKey() (*pem.Block, error) { return nil, ErrInvalidKEK{Wrapped: x509.IncorrectPasswordError} } - derBytes, err := keyutils.DecryptPEMBlock(keyBlock, k.kekData.KEK) - if err != nil { + derBytes, err := k.keyFormatter.DecryptPEMBlock(keyBlock, k.kekData.KEK) + if err == keyutils.ErrFIPSUnsupportedKeyFormat { + return nil, err + } else if err != nil { return nil, ErrInvalidKEK{Wrapped: err} } @@ -349,7 +360,7 @@ func (k *KeyReadWriter) readKey() (*pem.Block, error) { // writing it to disk. If the kek is nil, writes it to disk unencrypted. func (k *KeyReadWriter) writeKey(keyBlock *pem.Block, kekData KEKData, pkh PEMKeyHeaders) error { if kekData.KEK != nil { - encryptedPEMBlock, err := keyutils.EncryptPEMBlock(keyBlock.Bytes, kekData.KEK) + encryptedPEMBlock, err := k.keyFormatter.EncryptPEMBlock(keyBlock.Bytes, kekData.KEK) if err != nil { return err } @@ -404,7 +415,7 @@ func (k *KeyReadWriter) DowngradeKey() error { } if k.kekData.KEK != nil { - newBlock, err = keyutils.EncryptPEMBlock(newBlock.Bytes, k.kekData.KEK) + newBlock, err = k.keyFormatter.EncryptPEMBlock(newBlock.Bytes, k.kekData.KEK) if err != nil { return err } diff --git a/ca/keyreadwriter_test.go b/ca/keyreadwriter_test.go index 4b2610a74b..b28ee2c244 100644 --- a/ca/keyreadwriter_test.go +++ b/ca/keyreadwriter_test.go @@ -436,7 +436,7 @@ func testKeyReadWriterDowngradeKeyCase(t *testing.T, tc downgradeTestCase) error require.NotNil(t, block) kek = []byte("kek") - block, err = keyutils.EncryptPEMBlock(block.Bytes, kek) + block, err = keyutils.Default.EncryptPEMBlock(block.Bytes, kek) require.NoError(t, err) key = pem.EncodeToMemory(block) @@ -517,3 +517,47 @@ func TestKeyReadWriterDowngradeKey(t *testing.T) { require.NoError(t, err) } } + +// In FIPS mode, when reading a PKCS1 encrypted key, a PKCS1 error is returned as opposed +// to any other type of invalid KEK error +func TestKeyReadWriterReadNonFIPS(t *testing.T) { + t.Parallel() + cert, key, err := testutils.CreateRootCertAndKey("cn") + require.NoError(t, err) + + key, err = pkcs8.ConvertToECPrivateKeyPEM(key) + require.NoError(t, err) + + tempdir, err := ioutil.TempDir("", "KeyReadWriter") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + path := ca.NewConfigPaths(filepath.Join(tempdir, "subdir")) // to make sure subdirectories are created + + k := ca.NewKeyReadWriter(path.Node, nil, nil) + k.SetKeyFormatter(keyutils.FIPS) + + // can write an unencrypted PKCS1 key with no issues + require.NoError(t, k.Write(cert, key, nil)) + // can read the unencrypted key with no issues + readCert, readKey, err := k.Read() + require.NoError(t, err) + require.Equal(t, cert, readCert) + require.Equal(t, key, readKey) + + // cannot write an encrypted PKCS1 key + passphrase := []byte("passphrase") + require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, k.Write(cert, key, &ca.KEKData{KEK: passphrase})) + + k.SetKeyFormatter(keyutils.Default) + require.NoError(t, k.Write(cert, key, &ca.KEKData{KEK: passphrase})) + + // cannot read an encrypted PKCS1 key + k.SetKeyFormatter(keyutils.FIPS) + _, _, err = k.Read() + require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, err) + + k.SetKeyFormatter(keyutils.Default) + _, _, err = k.Read() + require.NoError(t, err) +} diff --git a/ca/keyutils/keyutils.go b/ca/keyutils/keyutils.go index ee6ad8fc16..ea45aab7dd 100644 --- a/ca/keyutils/keyutils.go +++ b/ca/keyutils/keyutils.go @@ -10,20 +10,30 @@ import ( "crypto/x509" "encoding/pem" "errors" - "os" "github.com/cloudflare/cfssl/helpers" "github.com/docker/swarmkit/ca/pkcs8" ) -var errFIPSUnsupportedKeyFormat = errors.New("unsupported key format due to FIPS compliance") +// Formatter provides an interface for converting keys to the right format, and encrypting and decrypting keys +type Formatter interface { + ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) + DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) + EncryptPEMBlock(data, password []byte) (*pem.Block, error) +} + +// ErrFIPSUnsupportedKeyFormat is returned when encryption/decryption operations are attempted on a PKCS1 key +// when FIPS mode is enabled. +var ErrFIPSUnsupportedKeyFormat = errors.New("unsupported key format due to FIPS compliance") + +// Default is the default key util, where FIPS is not required +var Default Formatter = &utils{fips: false} -// FIPSEnvVar is the environment variable which stores FIPS mode state -const FIPSEnvVar = "GOFIPS" +// FIPS is the key utility which enforces FIPS compliance +var FIPS Formatter = &utils{fips: true} -// FIPSEnabled returns true when FIPS mode is enabled -func FIPSEnabled() bool { - return os.Getenv(FIPSEnvVar) != "" +type utils struct { + fips bool } // IsPKCS8 returns true if the provided der bytes is encrypted/unencrypted PKCS#8 key @@ -39,9 +49,14 @@ func IsPKCS8(derBytes []byte) bool { }) } +// IsEncryptedPEMBlock checks if a PKCS#1 or PKCS#8 PEM-block is encrypted or not +func IsEncryptedPEMBlock(block *pem.Block) bool { + return pkcs8.IsEncryptedPEMBlock(block) || x509.IsEncryptedPEMBlock(block) +} + // ParsePrivateKeyPEMWithPassword parses an encrypted or a decrypted PKCS#1 or PKCS#8 PEM to crypto.Signer. // It returns an error in FIPS mode if PKCS#1 PEM bytes are passed. -func ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) { +func (u *utils) ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, error) { block, _ := pem.Decode(pemBytes) if block == nil { return nil, errors.New("Could not parse PEM") @@ -49,26 +64,20 @@ func ParsePrivateKeyPEMWithPassword(pemBytes, password []byte) (crypto.Signer, e if IsPKCS8(block.Bytes) { return pkcs8.ParsePrivateKeyPEMWithPassword(pemBytes, password) - } else if FIPSEnabled() { - return nil, errFIPSUnsupportedKeyFormat + } else if u.fips { + return nil, ErrFIPSUnsupportedKeyFormat } return helpers.ParsePrivateKeyPEMWithPassword(pemBytes, password) } -// IsEncryptedPEMBlock checks if a PKCS#1 or PKCS#8 PEM-block is encrypted or not -// It returns false in FIPS mode even if PKCS#1 is encrypted -func IsEncryptedPEMBlock(block *pem.Block) bool { - return pkcs8.IsEncryptedPEMBlock(block) || (!FIPSEnabled() && x509.IsEncryptedPEMBlock(block)) -} - // DecryptPEMBlock requires PKCS#1 or PKCS#8 PEM Block and password to decrypt and return unencrypted der []byte // It returns an error in FIPS mode when PKCS#1 PEM Block is passed. -func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { +func (u *utils) DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { if IsPKCS8(block.Bytes) { return pkcs8.DecryptPEMBlock(block, password) - } else if FIPSEnabled() { - return nil, errFIPSUnsupportedKeyFormat + } else if u.fips { + return nil, ErrFIPSUnsupportedKeyFormat } return x509.DecryptPEMBlock(block, password) @@ -76,11 +85,11 @@ func DecryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { // EncryptPEMBlock takes DER-format bytes and password to return an encrypted PKCS#1 or PKCS#8 PEM-block // It returns an error in FIPS mode when PKCS#1 PEM bytes are passed. -func EncryptPEMBlock(data, password []byte) (*pem.Block, error) { +func (u *utils) EncryptPEMBlock(data, password []byte) (*pem.Block, error) { if IsPKCS8(data) { return pkcs8.EncryptPEMBlock(data, password) - } else if FIPSEnabled() { - return nil, errFIPSUnsupportedKeyFormat + } else if u.fips { + return nil, ErrFIPSUnsupportedKeyFormat } cipherType := x509.PEMCipherAES256 diff --git a/ca/keyutils/keyutils_test.go b/ca/keyutils/keyutils_test.go index 15f52d69ef..d0b0d455a7 100644 --- a/ca/keyutils/keyutils_test.go +++ b/ca/keyutils/keyutils_test.go @@ -2,7 +2,6 @@ package keyutils import ( "encoding/pem" - "os" "testing" "github.com/stretchr/testify/assert" @@ -49,15 +48,6 @@ aMbljbOLAjpZS3/VnQteab4= encryptedPKCS1Block, _ = pem.Decode([]byte(encryptedPKCS1)) ) -func TestFIPSEnabled(t *testing.T) { - os.Unsetenv(FIPSEnvVar) - assert.False(t, FIPSEnabled()) - - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) - assert.True(t, FIPSEnabled()) -} - func TestIsPKCS8(t *testing.T) { // Check PKCS8 keys assert.True(t, IsPKCS8([]byte(decryptedPKCS8Block.Bytes))) @@ -69,125 +59,95 @@ func TestIsPKCS8(t *testing.T) { } func TestIsEncryptedPEMBlock(t *testing.T) { - // Disable FIPS mode - os.Unsetenv(FIPSEnvVar) - - // Check PKCS8 keys + // Check PKCS8 assert.False(t, IsEncryptedPEMBlock(decryptedPKCS8Block)) assert.True(t, IsEncryptedPEMBlock(encryptedPKCS8Block)) - // Check PKCS1 keys + // Check PKCS1 assert.False(t, IsEncryptedPEMBlock(decryptedPKCS1Block)) assert.True(t, IsEncryptedPEMBlock(encryptedPKCS1Block)) - - // Enable FIPS mode - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) - - // Check PKCS8 keys again - assert.False(t, IsEncryptedPEMBlock(decryptedPKCS8Block)) - assert.True(t, IsEncryptedPEMBlock(encryptedPKCS8Block)) - - // Check PKCS1 keys again - assert.False(t, IsEncryptedPEMBlock(decryptedPKCS1Block)) - assert.False(t, IsEncryptedPEMBlock(encryptedPKCS1Block)) } func TestDecryptPEMBlock(t *testing.T) { - // Disable FIPS mode - os.Unsetenv(FIPSEnvVar) - - // Check PKCS8 keys - _, err := DecryptPEMBlock(encryptedPKCS8Block, []byte("pony")) - require.Error(t, err) - - decryptedDer, err := DecryptPEMBlock(encryptedPKCS8Block, []byte("ponies")) - require.NoError(t, err) - require.Equal(t, decryptedPKCS8Block.Bytes, decryptedDer) - - // Check PKCS1 keys - _, err = DecryptPEMBlock(encryptedPKCS1Block, []byte("pony")) + // Check PKCS8 keys in both FIPS and non-FIPS mode + for _, util := range []Formatter{Default, FIPS} { + _, err := util.DecryptPEMBlock(encryptedPKCS8Block, []byte("pony")) + require.Error(t, err) + + decryptedDer, err := util.DecryptPEMBlock(encryptedPKCS8Block, []byte("ponies")) + require.NoError(t, err) + require.Equal(t, decryptedPKCS8Block.Bytes, decryptedDer) + } + + // Check PKCS1 keys in non-FIPS mode + _, err := Default.DecryptPEMBlock(encryptedPKCS1Block, []byte("pony")) require.Error(t, err) - decryptedDer, err = DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) + decryptedDer, err := Default.DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) require.NoError(t, err) require.Equal(t, decryptedPKCS1Block.Bytes, decryptedDer) - // Enable FIPS mode - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) - - // Try to decrypt PKCS1 - _, err = DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) + // Try to decrypt PKCS1 in FIPS + _, err = FIPS.DecryptPEMBlock(encryptedPKCS1Block, []byte("ponies")) require.Error(t, err) } func TestEncryptPEMBlock(t *testing.T) { - // Disable FIPS mode - os.Unsetenv(FIPSEnvVar) - - // Check PKCS8 keys - encryptedBlock, err := EncryptPEMBlock(decryptedPKCS8Block.Bytes, []byte("knock knock")) + // Check PKCS8 keys in both FIPS and non-FIPS mode + for _, util := range []Formatter{Default, FIPS} { + encryptedBlock, err := util.EncryptPEMBlock(decryptedPKCS8Block.Bytes, []byte("knock knock")) + require.NoError(t, err) + + // Try to decrypt the same encrypted block + _, err = util.DecryptPEMBlock(encryptedBlock, []byte("hey there")) + require.Error(t, err) + + decryptedDer, err := Default.DecryptPEMBlock(encryptedBlock, []byte("knock knock")) + require.NoError(t, err) + require.Equal(t, decryptedPKCS8Block.Bytes, decryptedDer) + } + + // Check PKCS1 keys in non FIPS mode + encryptedBlock, err := Default.EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) require.NoError(t, err) // Try to decrypt the same encrypted block - _, err = DecryptPEMBlock(encryptedBlock, []byte("hey there")) + _, err = Default.DecryptPEMBlock(encryptedBlock, []byte("hey there")) require.Error(t, err) - decryptedDer, err := DecryptPEMBlock(encryptedBlock, []byte("knock knock")) - require.NoError(t, err) - require.Equal(t, decryptedPKCS8Block.Bytes, decryptedDer) - - // Check PKCS1 keys - encryptedBlock, err = EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) - require.NoError(t, err) - - // Try to decrypt the same encrypted block - _, err = DecryptPEMBlock(encryptedBlock, []byte("hey there")) - require.Error(t, err) - - decryptedDer, err = DecryptPEMBlock(encryptedBlock, []byte("knock knock")) + decryptedDer, err := Default.DecryptPEMBlock(encryptedBlock, []byte("knock knock")) require.NoError(t, err) require.Equal(t, decryptedPKCS1Block.Bytes, decryptedDer) - // Enable FIPS mode - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) - // Try to encrypt PKCS1 - _, err = EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) + _, err = FIPS.EncryptPEMBlock(decryptedPKCS1Block.Bytes, []byte("knock knock")) require.Error(t, err) } func TestParsePrivateKeyPEMWithPassword(t *testing.T) { - // Disable FIPS mode - os.Unsetenv(FIPSEnvVar) + // Check PKCS8 keys in both FIPS and non-FIPS mode + for _, util := range []Formatter{Default, FIPS} { + _, err := util.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("pony")) + require.Error(t, err) - // Check PKCS8 keys - _, err := ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("pony")) - require.Error(t, err) + _, err = util.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("ponies")) + require.NoError(t, err) - _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS8), []byte("ponies")) - require.NoError(t, err) + _, err = util.ParsePrivateKeyPEMWithPassword([]byte(decryptedPKCS8), nil) + require.NoError(t, err) + } - _, err = ParsePrivateKeyPEMWithPassword([]byte(decryptedPKCS8), nil) - require.NoError(t, err) - - // Check PKCS1 keys - _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("pony")) + // Check PKCS1 keys in non-FIPS mode + _, err := Default.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("pony")) require.Error(t, err) - _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) + _, err = Default.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) require.NoError(t, err) - _, err = ParsePrivateKeyPEMWithPassword([]byte(decryptedPKCS1), nil) + _, err = Default.ParsePrivateKeyPEMWithPassword([]byte(decryptedPKCS1), nil) require.NoError(t, err) - // Enable FIPS mode - os.Setenv(FIPSEnvVar, "1") - defer os.Unsetenv(FIPSEnvVar) - - // Try to parse PKCS1 - _, err = ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) + // Try to parse PKCS1 in FIPS mode + _, err = FIPS.ParsePrivateKeyPEMWithPassword([]byte(encryptedPKCS1), []byte("ponies")) require.Error(t, err) } diff --git a/ca/reconciler.go b/ca/reconciler.go index 6daec76d39..d906475d9b 100644 --- a/ca/reconciler.go +++ b/ca/reconciler.go @@ -229,8 +229,8 @@ func (r *rootRotationReconciler) finishRootRotation(tx store.Tx, expectedRootCA CAKey: cluster.RootCA.RootRotation.CAKey, CACertHash: updatedRootCA.Digest.String(), JoinTokens: api.JoinTokens{ - Worker: GenerateJoinToken(&updatedRootCA), - Manager: GenerateJoinToken(&updatedRootCA), + Worker: GenerateJoinToken(&updatedRootCA, cluster.FIPS), + Manager: GenerateJoinToken(&updatedRootCA, cluster.FIPS), }, LastForcedRotation: cluster.RootCA.LastForcedRotation, } diff --git a/ca/server_test.go b/ca/server_test.go index 7b0b0da12c..996051960d 100644 --- a/ca/server_test.go +++ b/ca/server_test.go @@ -318,9 +318,9 @@ func TestNewNodeCertificateRequiresToken(t *testing.T) { ) assert.NoError(t, tc.MemoryStore.Update(func(tx store.Tx) error { clusters, _ := store.FindClusters(tx, store.ByName(store.DefaultClusterName)) - newWorkerToken = ca.GenerateJoinToken(&tc.RootCA) + newWorkerToken = ca.GenerateJoinToken(&tc.RootCA, false) clusters[0].RootCA.JoinTokens.Worker = newWorkerToken - newManagerToken = ca.GenerateJoinToken(&tc.RootCA) + newManagerToken = ca.GenerateJoinToken(&tc.RootCA, false) clusters[0].RootCA.JoinTokens.Manager = newManagerToken return store.UpdateCluster(tx, clusters[0]) })) diff --git a/ca/testutils/cautils.go b/ca/testutils/cautils.go index 2171d7f234..c3b086ed76 100644 --- a/ca/testutils/cautils.go +++ b/ca/testutils/cautils.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "os" + "strings" "testing" "time" @@ -111,17 +112,40 @@ func NewTestCA(t *testing.T, krwGenerators ...func(ca.CertPaths) *ca.KeyReadWrit CAKey: key, } - return NewTestCAFromAPIRootCA(t, tempdir, apiRootCA, krwGenerators) + return newTestCA(t, tempdir, apiRootCA, krwGenerators, false) +} + +// NewFIPSTestCA is a helper method that creates a mandatory fips TestCA and a bunch of default +// connections and security configs. +func NewFIPSTestCA(t *testing.T) *TestCA { + tempdir, err := ioutil.TempDir("", "swarm-ca-test-") + require.NoError(t, err) + + cert, key, err := CreateRootCertAndKey("swarm-test-CA") + require.NoError(t, err) + apiRootCA := api.RootCA{ + CACert: cert, + CAKey: key, + } + + return newTestCA(t, tempdir, apiRootCA, nil, true) } // NewTestCAFromAPIRootCA is a helper method that creates a TestCA and a bunch of default // connections and security configs, given a temp directory and an api.RootCA to use for creating // a cluster and for signing. func NewTestCAFromAPIRootCA(t *testing.T, tempBaseDir string, apiRootCA api.RootCA, krwGenerators []func(ca.CertPaths) *ca.KeyReadWriter) *TestCA { + return newTestCA(t, tempBaseDir, apiRootCA, krwGenerators, false) +} + +func newTestCA(t *testing.T, tempBaseDir string, apiRootCA api.RootCA, krwGenerators []func(ca.CertPaths) *ca.KeyReadWriter, fips bool) *TestCA { s := store.NewMemoryStore(&stateutils.MockProposer{}) paths := ca.NewConfigPaths(tempBaseDir) organization := identity.NewID() + if fips { + organization = "FIPS." + organization + } var ( externalSigningServer *ExternalSigningServer @@ -356,6 +380,7 @@ func genSecurityConfig(s *store.MemoryStore, rootCA ca.RootCA, krw *ca.KeyReadWr } func createClusterObject(t *testing.T, s *store.MemoryStore, clusterID string, apiRootCA api.RootCA, caRootCA *ca.RootCA, externalCAs ...*api.ExternalCA) *api.Cluster { + fips := strings.HasPrefix(clusterID, "FIPS.") cluster := &api.Cluster{ ID: clusterID, Spec: api.ClusterSpec{ @@ -367,12 +392,13 @@ func createClusterObject(t *testing.T, s *store.MemoryStore, clusterID string, a }, }, RootCA: apiRootCA, + FIPS: fips, } if cluster.RootCA.JoinTokens.Worker == "" { - cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(caRootCA) + cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(caRootCA, fips) } if cluster.RootCA.JoinTokens.Manager == "" { - cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(caRootCA) + cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(caRootCA, fips) } assert.NoError(t, s.Update(func(tx store.Tx) error { store.CreateCluster(tx, cluster) diff --git a/cmd/swarm-rafttool/common.go b/cmd/swarm-rafttool/common.go index 4d14eb2832..a169b9af6e 100644 --- a/cmd/swarm-rafttool/common.go +++ b/cmd/swarm-rafttool/common.go @@ -75,10 +75,12 @@ func decryptRaftData(swarmdir, outdir, unlockKey string) error { return err } - _, d := encryption.Defaults(deks.CurrentDEK) + // always use false for FIPS, since we want to be able to decrypt logs written using + // any algorithm (not just FIPS-compatible ones) + _, d := encryption.Defaults(deks.CurrentDEK, false) if deks.PendingDEK == nil { - _, d2 := encryption.Defaults(deks.PendingDEK) - d = storage.MultiDecrypter{d, d2} + _, d2 := encryption.Defaults(deks.PendingDEK, false) + d = encryption.NewMultiDecrypter(d, d2) } snapDir := filepath.Join(outdir, "snap-decrypted") diff --git a/cmd/swarm-rafttool/common_test.go b/cmd/swarm-rafttool/common_test.go index ada57263fd..606110ad20 100644 --- a/cmd/swarm-rafttool/common_test.go +++ b/cmd/swarm-rafttool/common_test.go @@ -74,7 +74,7 @@ func TestDecrypt(t *testing.T) { Term: 1, }, } - e, d := encryption.Defaults(dek) + e, d := encryption.Defaults(dek, false) writeFakeRaftData(t, tempdir, &origSnapshot, storage.NewWALFactory(e, d), storage.NewSnapFactory(e, d)) outdir := filepath.Join(tempdir, "outdir") diff --git a/cmd/swarm-rafttool/dump.go b/cmd/swarm-rafttool/dump.go index 97d90d5f1c..6360194d67 100644 --- a/cmd/swarm-rafttool/dump.go +++ b/cmd/swarm-rafttool/dump.go @@ -38,10 +38,12 @@ func loadData(swarmdir, unlockKey string) (*storage.WALData, *raftpb.Snapshot, e return nil, nil, err } - _, d := encryption.Defaults(deks.CurrentDEK) + // always set FIPS=false, because we want to decrypt logs stored using any + // algorithm, not just FIPS-compatible ones + _, d := encryption.Defaults(deks.CurrentDEK, false) if deks.PendingDEK == nil { - _, d2 := encryption.Defaults(deks.PendingDEK) - d = storage.MultiDecrypter{d, d2} + _, d2 := encryption.Defaults(deks.PendingDEK, false) + d = encryption.NewMultiDecrypter(d, d2) } walFactory = storage.NewWALFactory(encryption.NoopCrypter, d) diff --git a/integration/cluster.go b/integration/cluster.go index c08e7bbdaa..e46e01edbe 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -34,13 +34,14 @@ type testCluster struct { errs chan error wg sync.WaitGroup counter int + fips bool } var testnameKey struct{} // NewCluster creates new cluster to which nodes can be added. // AcceptancePolicy is set to most permissive mode on first manager node added. -func newTestCluster(testname string) *testCluster { +func newTestCluster(testname string, fips bool) *testCluster { ctx, cancel := context.WithCancel(context.Background()) ctx = context.WithValue(ctx, testnameKey, testname) c := &testCluster{ @@ -49,6 +50,7 @@ func newTestCluster(testname string) *testCluster { nodes: make(map[string]*testNode), nodesOrder: make(map[string]int), errs: make(chan error, 1024), + fips: fips, } c.api = &dummyAPI{c: c} return c @@ -92,7 +94,7 @@ func (c *testCluster) AddManager(lateBind bool, rootCA *ca.RootCA) error { // first node var n *testNode if len(c.nodes) == 0 { - node, err := newTestNode("", "", lateBind) + node, err := newTestNode("", "", lateBind, c.fips) if err != nil { return err } @@ -113,36 +115,17 @@ func (c *testCluster) AddManager(lateBind bool, rootCA *ca.RootCA) error { if err != nil { return err } - node, err := newTestNode(joinAddr, clusterInfo.RootCA.JoinTokens.Manager, false) + node, err := newTestNode(joinAddr, clusterInfo.RootCA.JoinTokens.Manager, false, c.fips) if err != nil { return err } n = node } - c.counter++ - ctx := log.WithLogger(c.ctx, log.L.WithFields( - logrus.Fields{ - "testnode": c.counter, - "testname": c.ctx.Value(testnameKey), - }, - )) - - c.wg.Add(1) - go func() { - c.errs <- n.node.Start(ctx) - c.wg.Done() - }() - - select { - case <-n.node.Ready(): - case <-time.After(opsTimeout): - return fmt.Errorf("node did not ready in time") + if err := c.AddNode(n); err != nil { + return err } - c.nodes[n.node.NodeID()] = n - c.nodesOrder[n.node.NodeID()] = c.counter - if lateBind { // Verify that the control API works if _, err := c.GetClusterInfo(); err != nil { @@ -157,7 +140,6 @@ func (c *testCluster) AddManager(lateBind bool, rootCA *ca.RootCA) error { // AddAgent adds node with Agent role(doesn't participate in raft cluster). func (c *testCluster) AddAgent() error { // first node - var n *testNode if len(c.nodes) == 0 { return fmt.Errorf("there is no manager nodes") } @@ -169,33 +151,61 @@ func (c *testCluster) AddAgent() error { if err != nil { return err } - node, err := newTestNode(joinAddr, clusterInfo.RootCA.JoinTokens.Worker, false) + node, err := newTestNode(joinAddr, clusterInfo.RootCA.JoinTokens.Worker, false, c.fips) if err != nil { return err } - n = node + return c.AddNode(node) +} +// AddNode adds a new node to the cluster +func (c *testCluster) AddNode(n *testNode) error { c.counter++ + if err := c.runNode(n, c.counter); err != nil { + c.counter-- + return err + } + c.nodes[n.node.NodeID()] = n + c.nodesOrder[n.node.NodeID()] = c.counter + return nil +} + +func (c *testCluster) runNode(n *testNode, nodeOrder int) error { ctx := log.WithLogger(c.ctx, log.L.WithFields( logrus.Fields{ - "testnode": c.counter, + "testnode": nodeOrder, "testname": c.ctx.Value(testnameKey), }, )) - c.wg.Add(1) + errCtx, cancel := context.WithCancel(context.Background()) + done := make(chan error) + defer cancel() + defer close(done) + + c.wg.Add(2) go func() { c.errs <- n.node.Start(ctx) c.wg.Done() }() + go func(n *node.Node) { + err := n.Err(errCtx) + select { + case <-errCtx.Done(): + default: + done <- err + } + c.wg.Done() + }(n.node) select { case <-n.node.Ready(): + case err := <-done: + return err case <-time.After(opsTimeout): - return fmt.Errorf("node is not ready in time") + return fmt.Errorf("node did not ready in time") } - c.nodes[n.node.NodeID()] = n - c.nodesOrder[n.node.NodeID()] = c.counter + return nil } @@ -343,40 +353,8 @@ func (c *testCluster) StartNode(id string) error { if !ok { return fmt.Errorf("set node role: node %s not found", id) } - - ctx := log.WithLogger(c.ctx, log.L.WithFields( - logrus.Fields{ - "testnode": c.nodesOrder[id], - "testname": c.ctx.Value(testnameKey), - }, - )) - - errCtx, cancel := context.WithCancel(context.Background()) - done := make(chan error) - defer cancel() - defer close(done) - - c.wg.Add(2) - go func() { - c.errs <- n.node.Start(ctx) - c.wg.Done() - }() - go func(n *node.Node) { - err := n.Err(errCtx) - select { - case <-errCtx.Done(): - default: - done <- err - } - c.wg.Done() - }(n.node) - - select { - case <-n.node.Ready(): - case err := <-done: + if err := c.runNode(n, c.nodesOrder[id]); err != nil { return err - case <-time.After(opsTimeout): - return fmt.Errorf("node did not ready in time") } if n.node.NodeID() != id { return fmt.Errorf("restarted node does not have have the same ID") diff --git a/integration/integration_test.go b/integration/integration_test.go index b559df986e..5fc6ef0efb 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -12,6 +12,8 @@ import ( "testing" "time" + "github.com/docker/swarmkit/node" + "golang.org/x/net/context" "reflect" @@ -20,7 +22,6 @@ import ( events "github.com/docker/go-events" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" - "github.com/docker/swarmkit/ca/keyutils" cautils "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/manager" @@ -154,7 +155,7 @@ func pollServiceReady(t *testing.T, c *testCluster, sid string, replicas int) { } func newCluster(t *testing.T, numWorker, numManager int) *testCluster { - cl := newTestCluster(t.Name()) + cl := newTestCluster(t.Name(), false) for i := 0; i < numManager; i++ { require.NoError(t, cl.AddManager(false, nil), "manager number %d", i+1) } @@ -166,8 +167,8 @@ func newCluster(t *testing.T, numWorker, numManager int) *testCluster { return cl } -func newClusterWithRootCA(t *testing.T, numWorker, numManager int, rootCA *ca.RootCA) *testCluster { - cl := newTestCluster(t.Name()) +func newClusterWithRootCA(t *testing.T, numWorker, numManager int, rootCA *ca.RootCA, fips bool) *testCluster { + cl := newTestCluster(t.Name(), fips) for i := 0; i < numManager; i++ { require.NoError(t, cl.AddManager(false, rootCA), "manager number %d", i+1) } @@ -194,7 +195,7 @@ func TestServiceCreateLateBind(t *testing.T) { numWorker, numManager := 3, 3 - cl := newTestCluster(t.Name()) + cl := newTestCluster(t.Name(), false) for i := 0; i < numManager; i++ { require.NoError(t, cl.AddManager(true, nil), "manager number %d", i+1) } @@ -268,19 +269,12 @@ func TestNodeOps(t *testing.T) { func TestAutolockManagers(t *testing.T) { t.Parallel() - // run this twice, once with root ca with pkcs1 key and then pkcs8 key - defer os.Unsetenv(keyutils.FIPSEnvVar) - for _, pkcs1 := range []bool{true, false} { - if pkcs1 { - os.Unsetenv(keyutils.FIPSEnvVar) - } else { - os.Setenv(keyutils.FIPSEnvVar, "1") - } - + // run this twice, once with FIPS set and once without FIPS set + for _, fips := range []bool{true, false} { rootCA, err := ca.CreateRootCA("rootCN") require.NoError(t, err) numWorker, numManager := 1, 1 - cl := newClusterWithRootCA(t, numWorker, numManager, &rootCA) + cl := newClusterWithRootCA(t, numWorker, numManager, &rootCA, fips) defer func() { require.NoError(t, cl.Stop()) }() @@ -551,7 +545,7 @@ func TestForceNewCluster(t *testing.T) { // start a new cluster with the external CA bootstrapped numWorker, numManager := 0, 1 - cl := newTestCluster(t.Name()) + cl := newTestCluster(t.Name(), false) defer func() { require.NoError(t, cl.Stop()) }() @@ -621,20 +615,13 @@ func pollRootRotationDone(t *testing.T, cl *testCluster) { func TestSuccessfulRootRotation(t *testing.T) { t.Parallel() - // run this twice, once with root ca with pkcs1 key and then pkcs8 key - defer os.Unsetenv(keyutils.FIPSEnvVar) - for _, pkcs1 := range []bool{true, false} { - if pkcs1 { - os.Unsetenv(keyutils.FIPSEnvVar) - } else { - os.Setenv(keyutils.FIPSEnvVar, "1") - } - + // run this twice, once with FIPS set and once without + for _, fips := range []bool{true, false} { rootCA, err := ca.CreateRootCA("rootCN") require.NoError(t, err) numWorker, numManager := 2, 3 - cl := newClusterWithRootCA(t, numWorker, numManager, &rootCA) + cl := newClusterWithRootCA(t, numWorker, numManager, &rootCA, fips) defer func() { require.NoError(t, cl.Stop()) }() @@ -858,7 +845,7 @@ func TestNodeJoinWithWrongCerts(t *testing.T) { require.NoError(t, err) for role, token := range tokens { - node, err := newTestNode(joinAddr, token, false) + node, err := newTestNode(joinAddr, token, false, false) require.NoError(t, err) nodeID := identity.NewID() require.NoError(t, @@ -872,3 +859,111 @@ func TestNodeJoinWithWrongCerts(t *testing.T) { require.Contains(t, err.Error(), "certificate signed by unknown authority") } } + +// If the cluster does not require FIPS, then any node can join and re-join +// regardless of FIPS mode. +func TestMixedFIPSClusterNonMandatoryFIPS(t *testing.T) { + t.Parallel() + + cl := newTestCluster(t.Name(), false) // no fips + defer func() { + require.NoError(t, cl.Stop()) + }() + // create cluster with a non-FIPS manager, add another non-FIPS manager and a non-FIPs worker + for i := 0; i < 2; i++ { + require.NoError(t, cl.AddManager(false, nil)) + } + require.NoError(t, cl.AddAgent()) + + // add a FIPS manager and FIPS worker + joinAddr, err := cl.RandomManager().node.RemoteAPIAddr() + require.NoError(t, err) + clusterInfo, err := cl.GetClusterInfo() + require.NoError(t, err) + for _, token := range []string{clusterInfo.RootCA.JoinTokens.Worker, clusterInfo.RootCA.JoinTokens.Manager} { + node, err := newTestNode(joinAddr, token, false, true) + require.NoError(t, err) + require.NoError(t, cl.AddNode(node)) + } + + pollClusterReady(t, cl, 2, 3) + + // switch which worker nodes are fips and which are not - all should start up just fine + // on managers, if we enable fips on a previously non-fips node, it won't be able to read + // non-fernet raft logs + for nodeID, n := range cl.nodes { + if n.IsManager() { + n.config.FIPS = false + } else { + n.config.FIPS = !n.config.FIPS + } + require.NoError(t, n.Pause(false)) + require.NoError(t, cl.StartNode(nodeID)) + } + + pollClusterReady(t, cl, 2, 3) +} + +// If the cluster require FIPS, then only FIPS nodes can join and re-join. +func TestMixedFIPSClusterMandatoryFIPS(t *testing.T) { + t.Parallel() + + cl := newTestCluster(t.Name(), true) + defer func() { + require.NoError(t, cl.Stop()) + }() + for i := 0; i < 3; i++ { + require.NoError(t, cl.AddManager(false, nil)) + } + require.NoError(t, cl.AddAgent()) + + pollClusterReady(t, cl, 1, 3) + + // restart a manager and restart the worker in non-FIPS mode - both will fail, but restarting it in FIPS mode + // will succeed + leader, err := cl.Leader() + require.NoError(t, err) + var nonLeader, worker *testNode + for _, n := range cl.nodes { + if n == leader { + continue + } + if nonLeader == nil && n.IsManager() { + nonLeader = n + } + if worker == nil && !n.IsManager() { + worker = n + } + } + for _, n := range []*testNode{nonLeader, worker} { + nodeID := n.node.NodeID() + rAddr := "" + if n.IsManager() { + // make sure to save the old address because if a node is stopped, we can't get the node address, and it gets set to + // a completely new address, which will break raft in the case of a manager + rAddr, err = n.node.RemoteAPIAddr() + require.NoError(t, err) + } + require.NoError(t, n.Pause(false)) + n.config.FIPS = false + require.Equal(t, node.ErrMandatoryFIPS, cl.StartNode(nodeID)) + + require.NoError(t, n.Pause(false)) + n.config.FIPS = true + n.config.ListenRemoteAPI = rAddr + require.NoError(t, cl.StartNode(nodeID)) + } + + pollClusterReady(t, cl, 1, 3) + + // try to add a non-FIPS manager and non-FIPS worker - it won't work + joinAddr, err := cl.RandomManager().node.RemoteAPIAddr() + require.NoError(t, err) + clusterInfo, err := cl.GetClusterInfo() + require.NoError(t, err) + for _, token := range []string{clusterInfo.RootCA.JoinTokens.Worker, clusterInfo.RootCA.JoinTokens.Manager} { + n, err := newTestNode(joinAddr, token, false, false) + require.NoError(t, err) + require.Equal(t, node.ErrMandatoryFIPS, cl.AddNode(n)) + } +} diff --git a/integration/node.go b/integration/node.go index 9ea075ec6f..6b2100bfdf 100644 --- a/integration/node.go +++ b/integration/node.go @@ -54,7 +54,7 @@ func generateCerts(tmpDir string, rootCA *ca.RootCA, nodeID, role, org string, w // existing cluster. if joinAddr is empty string, then new cluster will be initialized. // It uses TestExecutor as executor. If lateBind is set, the remote API port is not // bound. If rootCA is set, this root is used to bootstrap the node's TLS certs. -func newTestNode(joinAddr, joinToken string, lateBind bool) (*testNode, error) { +func newTestNode(joinAddr, joinToken string, lateBind bool, fips bool) (*testNode, error) { tmpDir, err := ioutil.TempDir("", "swarmkit-integration-") if err != nil { return nil, err @@ -67,6 +67,7 @@ func newTestNode(joinAddr, joinToken string, lateBind bool) (*testNode, error) { StateDir: tmpDir, Executor: &agentutils.TestExecutor{}, JoinToken: joinToken, + FIPS: fips, } if !lateBind { cfg.ListenRemoteAPI = "127.0.0.1:0" diff --git a/manager/controlapi/cluster.go b/manager/controlapi/cluster.go index 0876113d70..adc0c1a485 100644 --- a/manager/controlapi/cluster.go +++ b/manager/controlapi/cluster.go @@ -123,10 +123,10 @@ func (s *Server) UpdateCluster(ctx context.Context, request *api.UpdateClusterRe expireBlacklistedCerts(cluster) if request.Rotation.WorkerJoinToken { - cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(&rootCA) + cluster.RootCA.JoinTokens.Worker = ca.GenerateJoinToken(&rootCA, cluster.FIPS) } if request.Rotation.ManagerJoinToken { - cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(&rootCA) + cluster.RootCA.JoinTokens.Manager = ca.GenerateJoinToken(&rootCA, cluster.FIPS) } updatedRootCA, err := validateCAConfig(ctx, s.securityConfig, cluster) diff --git a/manager/controlapi/cluster_test.go b/manager/controlapi/cluster_test.go index e9c30ce25e..ad05a13596 100644 --- a/manager/controlapi/cluster_test.go +++ b/manager/controlapi/cluster_test.go @@ -46,8 +46,8 @@ func createClusterObj(id, name string, policy api.AcceptancePolicy, rootCA *ca.R CAKey: key, CACertHash: rootCA.Digest.String(), JoinTokens: api.JoinTokens{ - Worker: ca.GenerateJoinToken(rootCA), - Manager: ca.GenerateJoinToken(rootCA), + Worker: ca.GenerateJoinToken(rootCA, false), + Manager: ca.GenerateJoinToken(rootCA, false), }, }, } diff --git a/manager/deks.go b/manager/deks.go index 4813a67d53..f3d985a137 100644 --- a/manager/deks.go +++ b/manager/deks.go @@ -22,6 +22,10 @@ const ( type RaftDEKData struct { raft.EncryptionKeys NeedsRotation bool + + // The FIPS boolean is not serialized, but is internal state which indicates how + // the raft DEK headers should be encrypted (e.g. using FIPS compliant algorithms) + FIPS bool } // UnmarshalHeaders loads the state of the DEK manager given the current TLS headers @@ -32,13 +36,13 @@ func (r RaftDEKData) UnmarshalHeaders(headers map[string]string, kekData ca.KEKD ) if currentDEKStr, ok := headers[pemHeaderRaftDEK]; ok { - currentDEK, err = decodePEMHeaderValue(currentDEKStr, kekData.KEK) + currentDEK, err = decodePEMHeaderValue(currentDEKStr, kekData.KEK, r.FIPS) if err != nil { return nil, err } } if pendingDEKStr, ok := headers[pemHeaderRaftPendingDEK]; ok { - pendingDEK, err = decodePEMHeaderValue(pendingDEKStr, kekData.KEK) + pendingDEK, err = decodePEMHeaderValue(pendingDEKStr, kekData.KEK, r.FIPS) if err != nil { return nil, err } @@ -55,6 +59,7 @@ func (r RaftDEKData) UnmarshalHeaders(headers map[string]string, kekData ca.KEKD CurrentDEK: currentDEK, PendingDEK: pendingDEK, }, + FIPS: r.FIPS, }, nil } @@ -66,7 +71,7 @@ func (r RaftDEKData) MarshalHeaders(kekData ca.KEKData) (map[string]string, erro pemHeaderRaftPendingDEK: r.PendingDEK, } { if contents != nil { - dekStr, err := encodePEMHeaderValue(contents, kekData.KEK) + dekStr, err := encodePEMHeaderValue(contents, kekData.KEK, r.FIPS) if err != nil { return nil, err } @@ -88,6 +93,7 @@ func (r RaftDEKData) UpdateKEK(oldKEK, candidateKEK ca.KEKData) ca.PEMKeyHeaders return RaftDEKData{ EncryptionKeys: r.EncryptionKeys, NeedsRotation: true, + FIPS: r.FIPS, } } return r @@ -112,6 +118,7 @@ func compareKEKs(oldKEK, candidateKEK ca.KEKData) (bool, bool, error) { type RaftDEKManager struct { kw ca.KeyWriter rotationCh chan struct{} + FIPS bool } var errNoUpdateNeeded = fmt.Errorf("don't need to rotate or update") @@ -122,7 +129,7 @@ var errNotUsingRaftDEKData = fmt.Errorf("RaftDEKManager can no longer store and // NewRaftDEKManager returns a RaftDEKManager that uses the current key writer // and header manager -func NewRaftDEKManager(kw ca.KeyWriter) (*RaftDEKManager, error) { +func NewRaftDEKManager(kw ca.KeyWriter, fips bool) (*RaftDEKManager, error) { // If there is no current DEK, generate one and write it to disk err := kw.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) { dekData, ok := h.(RaftDEKData) @@ -132,6 +139,7 @@ func NewRaftDEKManager(kw ca.KeyWriter) (*RaftDEKManager, error) { EncryptionKeys: raft.EncryptionKeys{ CurrentDEK: encryption.GenerateSecretKey(), }, + FIPS: fips, }, nil } return nil, errNoUpdateNeeded @@ -141,6 +149,7 @@ func NewRaftDEKManager(kw ca.KeyWriter) (*RaftDEKManager, error) { } return &RaftDEKManager{ kw: kw, + FIPS: fips, rotationCh: make(chan struct{}, 1), }, nil } @@ -156,8 +165,9 @@ func (r *RaftDEKManager) NeedsRotation() bool { } // GetKeys returns the current set of DEKs. If NeedsRotation is true, and there -// is no existing PendingDEK, it will try to create one. If there are any errors -// doing so, just return the original. +// is no existing PendingDEK, it will try to create one. If it successfully creates +// and writes a PendingDEK, it sets NeedRotation to false. If there are any errors +// doing so, just return the original set of keys. func (r *RaftDEKManager) GetKeys() raft.EncryptionKeys { var newKeys, originalKeys raft.EncryptionKeys err := r.kw.ViewAndUpdateHeaders(func(h ca.PEMKeyHeaders) (ca.PEMKeyHeaders, error) { @@ -173,7 +183,10 @@ func (r *RaftDEKManager) GetKeys() raft.EncryptionKeys { CurrentDEK: data.CurrentDEK, PendingDEK: encryption.GenerateSecretKey(), } - return RaftDEKData{EncryptionKeys: newKeys}, nil + return RaftDEKData{ + EncryptionKeys: newKeys, + FIPS: data.FIPS, + }, nil }) if err != nil { return originalKeys @@ -202,6 +215,7 @@ func (r *RaftDEKManager) UpdateKeys(newKeys raft.EncryptionKeys) error { return RaftDEKData{ EncryptionKeys: newKeys, NeedsRotation: data.NeedsRotation, + FIPS: data.FIPS, }, nil }) } @@ -240,10 +254,10 @@ func (r *RaftDEKManager) MaybeUpdateKEK(candidateKEK ca.KEKData) (bool, bool, er return updated, unlockedToLocked, err } -func decodePEMHeaderValue(headerValue string, kek []byte) ([]byte, error) { +func decodePEMHeaderValue(headerValue string, kek []byte, fips bool) ([]byte, error) { var decrypter encryption.Decrypter = encryption.NoopCrypter if kek != nil { - _, decrypter = encryption.Defaults(kek) + _, decrypter = encryption.Defaults(kek, fips) } valueBytes, err := base64.StdEncoding.DecodeString(headerValue) if err != nil { @@ -256,10 +270,10 @@ func decodePEMHeaderValue(headerValue string, kek []byte) ([]byte, error) { return result, nil } -func encodePEMHeaderValue(headerValue []byte, kek []byte) (string, error) { +func encodePEMHeaderValue(headerValue []byte, kek []byte, fips bool) (string, error) { var encrypter encryption.Encrypter = encryption.NoopCrypter if kek != nil { - encrypter, _ = encryption.Defaults(kek) + encrypter, _ = encryption.Defaults(kek, fips) } encrypted, err := encryption.Encrypt(headerValue, encrypter) if err != nil { diff --git a/manager/deks_test.go b/manager/deks_test.go index 4873b05c62..63fdaf9141 100644 --- a/manager/deks_test.go +++ b/manager/deks_test.go @@ -3,6 +3,7 @@ package manager import ( "encoding/base64" "encoding/pem" + "fmt" "io/ioutil" "os" "testing" @@ -16,101 +17,121 @@ import ( // Tests updating a kek on a raftDEK object. func TestRaftDEKUpdateKEK(t *testing.T) { - startData := RaftDEKData{ - EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("first dek")}, + for _, fips := range []bool{true, false} { + startData := RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("first dek")}, + FIPS: fips, + } + startKEK := ca.KEKData{} + + // because UpdateKEK returns a PEMKeyHeaders interface, we need to cast to check + // values + updateDEKAndCast := func(dekdata RaftDEKData, oldKEK ca.KEKData, newKEK ca.KEKData) RaftDEKData { + result := dekdata.UpdateKEK(oldKEK, newKEK) + raftDekObj, ok := result.(RaftDEKData) + require.True(t, ok) + return raftDekObj + } + + // nothing changes if we are updating a kek and they're both nil + result := updateDEKAndCast(startData, startKEK, ca.KEKData{Version: 2}) + require.Equal(t, result, startData) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed + + // when moving from unlocked to locked, a "needs rotation" header is generated but no + // pending header is generated + updatedKEK := ca.KEKData{KEK: []byte("something"), Version: 1} + result = updateDEKAndCast(startData, startKEK, updatedKEK) + require.NotEqual(t, startData, result) + require.True(t, result.NeedsRotation) + require.Equal(t, startData.CurrentDEK, result.CurrentDEK) + require.Nil(t, result.PendingDEK) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed + + // this is whether or not pending exists + startData.PendingDEK = []byte("pending") + result = updateDEKAndCast(startData, startKEK, updatedKEK) + require.NotEqual(t, startData, result) + require.True(t, result.NeedsRotation) + require.Equal(t, startData.CurrentDEK, result.CurrentDEK) + require.Equal(t, startData.PendingDEK, result.PendingDEK) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed + + // if we are going from locked to unlocked, nothing happens + result = updateDEKAndCast(startData, updatedKEK, startKEK) + require.Equal(t, startData, result) + require.False(t, result.NeedsRotation) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed + + // if we are going to locked to another locked, nothing happens + result = updateDEKAndCast(startData, updatedKEK, ca.KEKData{KEK: []byte("other"), Version: 4}) + require.Equal(t, startData, result) + require.False(t, result.NeedsRotation) + require.Equal(t, startData.FIPS, result.FIPS) // fips value should not have changed } - startKEK := ca.KEKData{} - - // because UpdateKEK returns a PEMKeyHeaders interface, we need to cast to check - // values - updateDEKAndCast := func(dekdata RaftDEKData, oldKEK ca.KEKData, newKEK ca.KEKData) RaftDEKData { - result := dekdata.UpdateKEK(oldKEK, newKEK) - raftDekObj, ok := result.(RaftDEKData) - require.True(t, ok) - return raftDekObj - } - - // nothing changes if we are updating a kek and they're both nil - result := updateDEKAndCast(startData, startKEK, ca.KEKData{Version: 2}) - require.Equal(t, result, startData) - - // when moving from unlocked to locked, a "needs rotation" header is generated but no - // pending header is generated - updatedKEK := ca.KEKData{KEK: []byte("something"), Version: 1} - result = updateDEKAndCast(startData, startKEK, updatedKEK) - require.NotEqual(t, startData, result) - require.True(t, result.NeedsRotation) - require.Equal(t, startData.CurrentDEK, result.CurrentDEK) - require.Nil(t, result.PendingDEK) - - // this is whether or not pending exists - startData.PendingDEK = []byte("pending") - result = updateDEKAndCast(startData, startKEK, updatedKEK) - require.NotEqual(t, startData, result) - require.True(t, result.NeedsRotation) - require.Equal(t, startData.CurrentDEK, result.CurrentDEK) - require.Equal(t, startData.PendingDEK, result.PendingDEK) - - // if we are going from locked to unlocked, nothing happens - result = updateDEKAndCast(startData, updatedKEK, startKEK) - require.Equal(t, startData, result) - require.False(t, result.NeedsRotation) - - // if we are going to locked to another locked, nothing happens - result = updateDEKAndCast(startData, updatedKEK, ca.KEKData{KEK: []byte("other"), Version: 4}) - require.Equal(t, startData, result) - require.False(t, result.NeedsRotation) } func TestRaftDEKMarshalUnmarshal(t *testing.T) { - startData := RaftDEKData{ - EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("first dek")}, - } - kek := ca.KEKData{} - - headers, err := startData.MarshalHeaders(kek) - require.NoError(t, err) - require.Len(t, headers, 1) - - // can't unmarshal with the wrong kek - _, err = RaftDEKData{}.UnmarshalHeaders(headers, ca.KEKData{KEK: []byte("something")}) - require.Error(t, err) - - // we can unmarshal what was marshalled with the right kek - toData, err := RaftDEKData{}.UnmarshalHeaders(headers, kek) - require.NoError(t, err) - require.Equal(t, startData, toData) - - // try the other headers as well - startData.PendingDEK = []byte("Hello") - headers, err = startData.MarshalHeaders(kek) - require.NoError(t, err) - require.Len(t, headers, 2) - - // we can unmarshal what was marshalled - toData, err = RaftDEKData{}.UnmarshalHeaders(headers, kek) - require.NoError(t, err) - require.Equal(t, startData, toData) - - // try the other headers as well - startData.NeedsRotation = true - startData.PendingDEK = nil - headers, err = startData.MarshalHeaders(kek) - require.NoError(t, err) - require.Len(t, headers, 2) - - // we can unmarshal what was marshalled - toData, err = RaftDEKData{}.UnmarshalHeaders(headers, kek) - require.NoError(t, err) - require.Equal(t, startData, toData) - - // If there is a pending header, but no current header, set will fail - headers = map[string]string{ - pemHeaderRaftPendingDEK: headers[pemHeaderRaftDEK], + for _, fips := range []bool{true, false} { + startData := RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("first dek")}, + FIPS: fips, + } + kek := ca.KEKData{} + + headers, err := startData.MarshalHeaders(kek) + require.NoError(t, err) + require.Len(t, headers, 1) + + // can't unmarshal with the wrong kek + _, err = RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, ca.KEKData{KEK: []byte("something")}) + require.Error(t, err) + + // we can unmarshal what was marshalled with the right kek + toData, err := RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, kek) + require.NoError(t, err) + require.Equal(t, startData, toData) + casted, ok := toData.(RaftDEKData) + require.True(t, ok) + require.Equal(t, fips, casted.FIPS) // fips value should not have changed + + // try the other headers as well + startData.PendingDEK = []byte("Hello") + headers, err = startData.MarshalHeaders(kek) + require.NoError(t, err) + require.Len(t, headers, 2) + + // we can unmarshal what was marshalled + toData, err = RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, kek) + require.NoError(t, err) + require.Equal(t, startData, toData) + casted, ok = toData.(RaftDEKData) + require.True(t, ok) + require.Equal(t, fips, casted.FIPS) // fips value should not have changed + + // try the other headers as well + startData.NeedsRotation = true + startData.PendingDEK = nil + headers, err = startData.MarshalHeaders(kek) + require.NoError(t, err) + require.Len(t, headers, 2) + + // we can unmarshal what was marshalled + toData, err = RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, kek) + require.NoError(t, err) + require.Equal(t, startData, toData) + casted, ok = toData.(RaftDEKData) + require.True(t, ok) + require.Equal(t, fips, casted.FIPS) // fips value should not have changed + + // If there is a pending header, but no current header, set will fail + headers = map[string]string{ + pemHeaderRaftPendingDEK: headers[pemHeaderRaftDEK], + } + _, err = RaftDEKData{FIPS: fips}.UnmarshalHeaders(headers, kek) + require.Error(t, err) + require.Contains(t, err.Error(), "pending DEK, but no current DEK") } - _, err = RaftDEKData{}.UnmarshalHeaders(headers, kek) - require.Error(t, err) - require.Contains(t, err.Error(), "pending DEK, but no current DEK") } // NewRaftDEKManager creates a key if one doesn't exist @@ -123,38 +144,50 @@ func TestNewRaftDEKManager(t *testing.T) { cert, key, err := cautils.CreateRootCertAndKey("cn") require.NoError(t, err) - krw := ca.NewKeyReadWriter(paths.Node, nil, nil) - require.NoError(t, krw.Write(cert, key, nil)) + for _, fips := range []bool{true, false} { + krw := ca.NewKeyReadWriter(paths.Node, nil, nil) + require.NoError(t, krw.Write(cert, key, nil)) - keyBytes, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.NotContains(t, string(keyBytes), pemHeaderRaftDEK) // headers are not written + keyBytes, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.NotContains(t, string(keyBytes), pemHeaderRaftDEK) // headers are not written - dekManager, err := NewRaftDEKManager(krw) // this should create a new DEK and write it to the file - require.NoError(t, err) + dekManager, err := NewRaftDEKManager(krw, fips) // this should create a new DEK and write it to the file + require.NoError(t, err) - keyBytes, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Contains(t, string(keyBytes), pemHeaderRaftDEK) // header is written now + keyBytes, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Contains(t, string(keyBytes), pemHeaderRaftDEK) // header is written now - keys := dekManager.GetKeys() - require.NotNil(t, keys.CurrentDEK) - require.Nil(t, keys.PendingDEK) - require.False(t, dekManager.NeedsRotation()) + // ensure that the created raft DEK uses FIPS + h, _ := krw.GetCurrentState() + casted, ok := h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, fips, casted.FIPS) - // If one exists, nothing is updated - dekManager, err = NewRaftDEKManager(krw) // this should create a new DEK and write it to the file - require.NoError(t, err) + keys := dekManager.GetKeys() + require.NotNil(t, keys.CurrentDEK) + require.Nil(t, keys.PendingDEK) + require.False(t, dekManager.NeedsRotation()) - keyBytes2, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Equal(t, keyBytes, keyBytes2) + // If one exists, nothing is updated + dekManager, err = NewRaftDEKManager(krw, fips) // this should not have created a new dek + require.NoError(t, err) - require.Equal(t, keys, dekManager.GetKeys()) - require.False(t, dekManager.NeedsRotation()) + keyBytes2, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Equal(t, keyBytes, keyBytes2) + + require.Equal(t, keys, dekManager.GetKeys()) + require.False(t, dekManager.NeedsRotation()) + } } -// NeedsRotate returns true if there is a PendingDEK or a NeedsRotation flag +// NeedsRotate returns true if there is a PendingDEK or a NeedsRotation flag. GetKeys() evaluates +// whether a PendingDEK is there, and if there's no pending DEK but there is a NeedsRotation flag, +// it creates a PendingDEK and removes the NeedsRotation flag. If both the PendingDEK and +// NeedsRotation flag are there, it does not remove the NeedsRotation flag, because that indicates +// that we basically need to do 2 rotations. func TestRaftDEKManagerNeedsRotateGetKeys(t *testing.T) { tempDir, err := ioutil.TempDir("", "manager-maybe-get-data-") require.NoError(t, err) @@ -162,80 +195,110 @@ func TestRaftDEKManagerNeedsRotateGetKeys(t *testing.T) { paths := ca.NewConfigPaths(tempDir) - // if there is no PendingDEK, and no NeedsRotation flag: NeedsRotation=false - keys := raft.EncryptionKeys{CurrentDEK: []byte("hello")} - dekManager, err := NewRaftDEKManager( - ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys})) - require.NoError(t, err) - - require.False(t, dekManager.NeedsRotation()) - require.Equal(t, keys, dekManager.GetKeys()) - - // if there is a PendingDEK, and no NeedsRotation flag: NeedsRotation=true - keys = raft.EncryptionKeys{CurrentDEK: []byte("hello"), PendingDEK: []byte("another")} - dekManager, err = NewRaftDEKManager( - ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys})) - require.NoError(t, err) - - require.True(t, dekManager.NeedsRotation()) - require.Equal(t, keys, dekManager.GetKeys()) - - // if there is a PendingDEK, and a NeedsRotation flag: NeedsRotation=true - keys = raft.EncryptionKeys{CurrentDEK: []byte("hello"), PendingDEK: []byte("another")} - dekManager, err = NewRaftDEKManager( - ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ - EncryptionKeys: keys, - NeedsRotation: true, - })) - require.NoError(t, err) - - require.True(t, dekManager.NeedsRotation()) - require.Equal(t, keys, dekManager.GetKeys()) - - // if there no PendingDEK, and a NeedsRotation flag: NeedsRotation=true and - // GetKeys attempts to create a pending key and write it to disk. However, writing - // will error (because there is no key on disk atm), and then the original keys will - // be returned. - keys = raft.EncryptionKeys{CurrentDEK: []byte("hello")} - krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ - EncryptionKeys: keys, - NeedsRotation: true, - }) - dekManager, err = NewRaftDEKManager(krw) - require.NoError(t, err) - - require.True(t, dekManager.NeedsRotation()) - require.Equal(t, keys, dekManager.GetKeys()) - h, _ := krw.GetCurrentState() - dekData, ok := h.(RaftDEKData) - require.True(t, ok) - require.True(t, dekData.NeedsRotation) - - // if there no PendingDEK, and a NeedsRotation flag: NeedsRotation=true and - // GetKeys attempts to create a pending key and write it to disk. If successful, - // it returns the new keys - keys = raft.EncryptionKeys{CurrentDEK: []byte("hello")} - krw = ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ - EncryptionKeys: keys, - NeedsRotation: true, - }) - dekManager, err = NewRaftDEKManager(krw) - - require.NoError(t, err) - cert, key, err := cautils.CreateRootCertAndKey("cn") - require.NoError(t, err) - require.NoError(t, krw.Write(cert, key, nil)) - - require.True(t, dekManager.NeedsRotation()) - updatedKeys := dekManager.GetKeys() - require.Equal(t, keys.CurrentDEK, updatedKeys.CurrentDEK) - require.NotNil(t, updatedKeys.PendingDEK) - require.True(t, dekManager.NeedsRotation()) - - h, _ = krw.GetCurrentState() - dekData, ok = h.(RaftDEKData) - require.True(t, ok) - require.False(t, dekData.NeedsRotation) + for _, fips := range []bool{true, false} { + for _, testcase := range []struct { + description string + dekData RaftDEKData + managerNeedsRotation bool + newDEKDataNeedsRotation bool + keyOnDisk bool + }{ + { + description: "if there is no PendingDEK, and no NeedsRotation flag: NeedsRotation()->false, DEKData.NeedsRotation->false", + keyOnDisk: true, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("hello")}, + NeedsRotation: false, + }, + managerNeedsRotation: false, + newDEKDataNeedsRotation: false, + }, + { + description: "if there is a PendingDEK, and no NeedsRotation flag: NeedsRotation()->true, DEKData.NeedsRotation->false", + keyOnDisk: true, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{ + CurrentDEK: []byte("hello"), + PendingDEK: []byte("another"), + }, + NeedsRotation: false, + }, + managerNeedsRotation: true, + newDEKDataNeedsRotation: false, + }, + { + description: "if there is a PendingDEK, and a NeedsRotation flag: NeedsRotation()->true, DEKData.NeedsRotation->true", + keyOnDisk: true, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{ + CurrentDEK: []byte("hello"), + PendingDEK: []byte("another"), + }, + NeedsRotation: true, + }, + managerNeedsRotation: true, + newDEKDataNeedsRotation: true, + }, + // These in these two cases, the original keys did not have pending keys. GetKeys + // should create them, but only if it can write the new pending key to the disk. + { + description: ` + if there no PendingDEK, and a NeedsRotation flag: NeedsRotation()->true and + GetKeys attempts to create a pending key and write it to disk. However, writing + will error (because there is no key on disk atm), and then the original keys will + be returned. So DEKData.NeedsRotation->true.`, + keyOnDisk: false, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("hello")}, + NeedsRotation: true, + }, + managerNeedsRotation: true, + newDEKDataNeedsRotation: true, + }, + { + description: ` + if there no PendingDEK, and there is a NeedsRotation flag: NeedsRotation()->true and + GetKeys attempts to create a pending key and write it to disk. Once a pending key is + created, the NeedsRotation flag can be set to false. So DEKData.NeedsRotation->false`, + keyOnDisk: true, + dekData: RaftDEKData{ + EncryptionKeys: raft.EncryptionKeys{CurrentDEK: []byte("hello")}, + NeedsRotation: true, + }, + managerNeedsRotation: true, + newDEKDataNeedsRotation: false, + }, + } { + // clear the directory + require.NoError(t, os.RemoveAll(tempDir)) + os.Mkdir(tempDir, 0777) + testcase.dekData.FIPS = fips + krw := ca.NewKeyReadWriter(paths.Node, nil, testcase.dekData) + if testcase.keyOnDisk { + cert, key, err := cautils.CreateRootCertAndKey("cn") + require.NoError(t, err) + require.NoError(t, krw.Write(cert, key, nil)) + } + dekManager, err := NewRaftDEKManager(krw, fips) + require.NoError(t, err) + + require.Equal(t, testcase.managerNeedsRotation, dekManager.NeedsRotation(), testcase.description) + + gotKeys := dekManager.GetKeys() + if testcase.dekData.NeedsRotation && testcase.dekData.EncryptionKeys.PendingDEK == nil && testcase.keyOnDisk { + require.Equal(t, testcase.dekData.EncryptionKeys.CurrentDEK, gotKeys.CurrentDEK, testcase.description) + require.NotNil(t, gotKeys.PendingDEK, testcase.description) + } else { + require.Equal(t, testcase.dekData.EncryptionKeys, gotKeys, testcase.description) + } + + h, _ := krw.GetCurrentState() + dekData, ok := h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, testcase.newDEKDataNeedsRotation, dekData.NeedsRotation, + "(FIPS: %v) %s", fips, testcase.description) + } + } } func TestRaftDEKManagerUpdateKeys(t *testing.T) { @@ -251,42 +314,46 @@ func TestRaftDEKManagerUpdateKeys(t *testing.T) { CurrentDEK: []byte("key1"), PendingDEK: []byte("key2"), } - krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ - EncryptionKeys: keys, - NeedsRotation: true, - }) - require.NoError(t, krw.Write(cert, key, nil)) + for _, fips := range []bool{true, false} { + krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ + EncryptionKeys: keys, + NeedsRotation: true, + FIPS: fips, + }) + require.NoError(t, krw.Write(cert, key, nil)) - dekManager, err := NewRaftDEKManager(krw) - require.NoError(t, err) + dekManager, err := NewRaftDEKManager(krw, fips) + require.NoError(t, err) - newKeys := raft.EncryptionKeys{ - CurrentDEK: []byte("new current"), - } - require.NoError(t, dekManager.UpdateKeys(newKeys)) - // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one + newKeys := raft.EncryptionKeys{ + CurrentDEK: []byte("new current"), + } + require.NoError(t, dekManager.UpdateKeys(newKeys)) + // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one - h, _ := krw.GetCurrentState() - dekData, ok := h.(RaftDEKData) - require.True(t, ok) - require.True(t, dekData.NeedsRotation) + h, _ := krw.GetCurrentState() + dekData, ok := h.(RaftDEKData) + require.True(t, ok) + require.True(t, dekData.NeedsRotation) + require.Equal(t, fips, dekData.FIPS) - // UpdateKeys so there is no CurrentDEK: all the headers should be wiped out - require.NoError(t, dekManager.UpdateKeys(raft.EncryptionKeys{})) - require.Equal(t, raft.EncryptionKeys{}, dekManager.GetKeys()) - require.False(t, dekManager.NeedsRotation()) + // UpdateKeys so there is no CurrentDEK: all the headers should be wiped out + require.NoError(t, dekManager.UpdateKeys(raft.EncryptionKeys{})) + require.Equal(t, raft.EncryptionKeys{}, dekManager.GetKeys()) + require.False(t, dekManager.NeedsRotation()) - h, _ = krw.GetCurrentState() - require.Nil(t, h) + h, _ = krw.GetCurrentState() + require.Nil(t, h) - keyBytes, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - keyBlock, _ := pem.Decode(keyBytes) - require.NotNil(t, keyBlock) + keyBytes, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + keyBlock, _ := pem.Decode(keyBytes) + require.NotNil(t, keyBlock) - // the only header remaining should be the kek version - require.Len(t, keyBlock.Headers, 1) - require.Contains(t, keyBlock.Headers, "kek-version") + // the only header remaining should be the kek version + require.Len(t, keyBlock.Headers, 1) + require.Contains(t, keyBlock.Headers, "kek-version") + } } func TestRaftDEKManagerMaybeUpdateKEK(t *testing.T) { @@ -300,101 +367,112 @@ func TestRaftDEKManagerMaybeUpdateKEK(t *testing.T) { keys := raft.EncryptionKeys{CurrentDEK: []byte("current dek")} - // trying to update a KEK will error if the version is the same but the kek is different - krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys}) - require.NoError(t, krw.Write(cert, key, nil)) - dekManager, err := NewRaftDEKManager(krw) - require.NoError(t, err) - - keyBytes, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - - _, _, err = dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now")}) - require.Error(t, err) - require.False(t, dekManager.NeedsRotation()) - - keyBytes2, err := ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Equal(t, keyBytes, keyBytes2) - - // trying to update a KEK from unlocked to lock will set NeedsRotation to true, as well as encrypt the TLS key - updated, unlockedToLocked, err := dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now"), Version: 1}) - require.NoError(t, err) - require.True(t, updated) - require.True(t, unlockedToLocked) - // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one - h, _ := krw.GetCurrentState() - dekData, ok := h.(RaftDEKData) - require.True(t, ok) - require.Equal(t, keys, dekData.EncryptionKeys) - require.True(t, dekData.NeedsRotation) - require.NotNil(t, <-dekManager.RotationNotify()) // we are notified of a new pending key - - keyBytes2, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.NotEqual(t, keyBytes, keyBytes2) - keyBytes = keyBytes2 - - readKRW := ca.NewKeyReadWriter(paths.Node, []byte("locked now"), RaftDEKData{}) - _, _, err = readKRW.Read() - require.NoError(t, err) - - // trying to update a KEK of a lower version will not update anything, but will not error - updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{}) - require.NoError(t, err) - require.False(t, unlockedToLocked) - require.False(t, updated) - // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one - h, _ = krw.GetCurrentState() - dekData, ok = h.(RaftDEKData) - require.True(t, ok) - require.Equal(t, keys, dekData.EncryptionKeys) - require.True(t, dekData.NeedsRotation) - - keyBytes2, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Equal(t, keyBytes, keyBytes2, string(keyBytes), string(keyBytes2)) - - // updating a kek to a higher version, but with the same kek, will also neither update anything nor error - updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now"), Version: 100}) - require.NoError(t, err) - require.False(t, unlockedToLocked) - require.False(t, updated) - // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one - h, _ = krw.GetCurrentState() - dekData, ok = h.(RaftDEKData) - require.True(t, ok) - require.Equal(t, keys, dekData.EncryptionKeys) - require.True(t, dekData.NeedsRotation) - - keyBytes2, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.Equal(t, keyBytes, keyBytes2) - - // going from locked to unlock does not result in the NeedsRotation flag, but does result in - // the key being decrypted - krw = ca.NewKeyReadWriter(paths.Node, []byte("kek"), RaftDEKData{EncryptionKeys: keys}) - require.NoError(t, krw.Write(cert, key, nil)) - dekManager, err = NewRaftDEKManager(krw) - require.NoError(t, err) - - keyBytes, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) + for _, fips := range []bool{true, false} { + // trying to update a KEK will error if the version is the same but the kek is different + krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{ + EncryptionKeys: keys, + FIPS: fips, + }) + require.NoError(t, krw.Write(cert, key, nil)) + dekManager, err := NewRaftDEKManager(krw, fips) + require.NoError(t, err) + + keyBytes, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + + _, _, err = dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now")}) + require.Error(t, err) + require.False(t, dekManager.NeedsRotation()) + + keyBytes2, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Equal(t, keyBytes, keyBytes2) + + // trying to update a KEK from unlocked to lock will set NeedsRotation to true, as well as encrypt the TLS key + updated, unlockedToLocked, err := dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now"), Version: 1}) + require.NoError(t, err) + require.True(t, updated) + require.True(t, unlockedToLocked) + // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one + h, _ := krw.GetCurrentState() + dekData, ok := h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, keys, dekData.EncryptionKeys) + require.True(t, dekData.NeedsRotation) + require.Equal(t, fips, dekData.FIPS) + require.NotNil(t, <-dekManager.RotationNotify()) // we are notified of a new pending key + + keyBytes2, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.NotEqual(t, keyBytes, keyBytes2) + keyBytes = keyBytes2 + + readKRW := ca.NewKeyReadWriter(paths.Node, []byte("locked now"), RaftDEKData{FIPS: fips}) + _, _, err = readKRW.Read() + require.NoError(t, err) + + // trying to update a KEK of a lower version will not update anything, but will not error + updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{}) + require.NoError(t, err) + require.False(t, unlockedToLocked) + require.False(t, updated) + // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one + h, _ = krw.GetCurrentState() + dekData, ok = h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, keys, dekData.EncryptionKeys) + require.True(t, dekData.NeedsRotation) + require.Equal(t, fips, dekData.FIPS) + + keyBytes2, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Equal(t, keyBytes, keyBytes2, string(keyBytes), string(keyBytes2)) + + // updating a kek to a higher version, but with the same kek, will also neither update anything nor error + updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{KEK: []byte("locked now"), Version: 100}) + require.NoError(t, err) + require.False(t, unlockedToLocked) + require.False(t, updated) + // don't run GetKeys, because NeedsRotation is true and it'd just generate a new one + h, _ = krw.GetCurrentState() + dekData, ok = h.(RaftDEKData) + require.True(t, ok) + require.Equal(t, keys, dekData.EncryptionKeys) + require.True(t, dekData.NeedsRotation) + require.Equal(t, fips, dekData.FIPS) - updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{Version: 2}) - require.NoError(t, err) - require.False(t, unlockedToLocked) - require.True(t, updated) - require.Equal(t, keys, dekManager.GetKeys()) - require.False(t, dekManager.NeedsRotation()) + keyBytes2, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.Equal(t, keyBytes, keyBytes2) - keyBytes2, err = ioutil.ReadFile(paths.Node.Key) - require.NoError(t, err) - require.NotEqual(t, keyBytes, keyBytes2) - - readKRW = ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{}) - _, _, err = readKRW.Read() - require.NoError(t, err) + // going from locked to unlock does not result in the NeedsRotation flag, but does result in + // the key being decrypted + krw = ca.NewKeyReadWriter(paths.Node, []byte("kek"), RaftDEKData{ + EncryptionKeys: keys, + FIPS: fips, + }) + require.NoError(t, krw.Write(cert, key, nil)) + dekManager, err = NewRaftDEKManager(krw, fips) + require.NoError(t, err) + + keyBytes, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + + updated, unlockedToLocked, err = dekManager.MaybeUpdateKEK(ca.KEKData{Version: 2}) + require.NoError(t, err) + require.False(t, unlockedToLocked) + require.True(t, updated) + require.Equal(t, keys, dekManager.GetKeys()) + require.False(t, dekManager.NeedsRotation()) + + keyBytes2, err = ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + require.NotEqual(t, keyBytes, keyBytes2) + + readKRW = ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{FIPS: fips}) + _, _, err = readKRW.Read() + require.NoError(t, err) + } } // The TLS KEK and the KEK for the headers should be in sync, and so failing @@ -461,3 +539,34 @@ O0T3aXuZGYNyh//KqAoA3erCmh6HauMz84Y= _, _, err = krw.Read() require.NoError(t, err) } + +// If FIPS is enabled, the raft DEK will be encrypted using fernet, and not NACL secretbox. +func TestRaftDEKsFIPSEnabledUsesFernet(t *testing.T) { + tempDir, err := ioutil.TempDir("", "manager-dek-fips") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + paths := ca.NewConfigPaths(tempDir) + cert, key, err := cautils.CreateRootCertAndKey("cn") + require.NoError(t, err) + + // no particular reason not to use FIPS in the key writer to write the TLS key itself, + // except to demonstrate that these two functionalities are decoupled + keys := raft.EncryptionKeys{CurrentDEK: []byte("current dek")} + krw := ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys, FIPS: true}) + require.NoError(t, krw.Write(cert, key, nil)) + + dekManager, err := NewRaftDEKManager(krw, true) // this should be able to read the dek data + require.NoError(t, err) + require.Equal(t, keys, dekManager.GetKeys()) + + // if we do not use FIPS to write the header in the first place, a FIPS DEK manager can't read it + // because it's NACL secretbox + keys = raft.EncryptionKeys{CurrentDEK: []byte("current dek")} + krw = ca.NewKeyReadWriter(paths.Node, nil, RaftDEKData{EncryptionKeys: keys}) + require.NoError(t, krw.Write(cert, key, nil)) + + dekManager, err = NewRaftDEKManager(krw, true) // this should be able to read the dek data + require.NoError(t, err) + fmt.Println(err) +} diff --git a/manager/encryption/encryption.go b/manager/encryption/encryption.go index 7461631b33..d9aad6ad84 100644 --- a/manager/encryption/encryption.go +++ b/manager/encryption/encryption.go @@ -59,6 +59,60 @@ func (n noopCrypter) Algorithm() api.MaybeEncryptedRecord_Algorithm { // decrypt any data var NoopCrypter = noopCrypter{} +// specificDecryptor represents a specific type of Decrypter, like NaclSecretbox or Fernet. +// It does not apply to a more general decrypter like MultiDecrypter. +type specificDecrypter interface { + Decrypter + Algorithm() api.MaybeEncryptedRecord_Algorithm +} + +// MultiDecrypter is a decrypter that will attempt to decrypt with multiple decrypters. It +// references them by algorithm, so that only the relevant decrypters are checked instead of +// every single one. The reason for multiple decrypters per algorithm is to support hitless +// encryption key rotation. +// +// For raft encryption for instance, during an encryption key rotation, it's possible to have +// some raft logs encrypted with the old key and some encrypted with the new key, so we need a +// decrypter that can decrypt both. +type MultiDecrypter struct { + decrypters map[api.MaybeEncryptedRecord_Algorithm][]Decrypter +} + +// Decrypt tries to decrypt using any decrypters that match the given algorithm. +func (m MultiDecrypter) Decrypt(r api.MaybeEncryptedRecord) (result []byte, err error) { + decrypters, ok := m.decrypters[r.Algorithm] + if !ok { + return nil, fmt.Errorf("cannot decrypt record encrypted using %s", + api.MaybeEncryptedRecord_Algorithm_name[int32(r.Algorithm)]) + } + for _, d := range decrypters { + result, err = d.Decrypt(r) + if err == nil { + return + } + } + return +} + +// NewMultiDecrypter returns a new MultiDecrypter given multiple Decrypters. If any of +// the Decrypters are also MultiDecrypters, they are flattened into a single map, but +// it does not deduplicate any decrypters. +// Note that if something is neither a MultiDecrypter nor a specificDecrypter, it is +// ignored. +func NewMultiDecrypter(decrypters ...Decrypter) MultiDecrypter { + m := MultiDecrypter{decrypters: make(map[api.MaybeEncryptedRecord_Algorithm][]Decrypter)} + for _, d := range decrypters { + if md, ok := d.(MultiDecrypter); ok { + for algo, dec := range md.decrypters { + m.decrypters[algo] = append(m.decrypters[algo], dec...) + } + } else if sd, ok := d.(specificDecrypter); ok { + m.decrypters[sd.Algorithm()] = append(m.decrypters[sd.Algorithm()], sd) + } + } + return m +} + // Decrypt turns a slice of bytes serialized as an MaybeEncryptedRecord into a slice of plaintext bytes func Decrypt(encryptd []byte, decrypter Decrypter) ([]byte, error) { if decrypter == nil { @@ -95,10 +149,15 @@ func Encrypt(plaintext []byte, encrypter Encrypter) ([]byte, error) { return data, nil } -// Defaults returns a default encrypter and decrypter -func Defaults(key []byte) (Encrypter, Decrypter) { +// Defaults returns a default encrypter and decrypter. If the FIPS parameter is set to +// true, the only algorithm supported on both the encrypter and decrypter will be fernet. +func Defaults(key []byte, fips bool) (Encrypter, Decrypter) { + f := NewFernet(key) + if fips { + return f, f + } n := NewNACLSecretbox(key) - return n, n + return n, NewMultiDecrypter(n, f) } // GenerateSecretKey generates a secret key that can be used for encrypting data diff --git a/manager/encryption/encryption_test.go b/manager/encryption/encryption_test.go index 14f2d9a728..2556ef8abe 100644 --- a/manager/encryption/encryption_test.go +++ b/manager/encryption/encryption_test.go @@ -1,6 +1,7 @@ package encryption import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -28,7 +29,7 @@ func TestEncryptDecrypt(t *testing.T) { require.Equal(t, msg, decrypted) // the default encrypter can produce something the default decrypter can read - encrypter, decrypter := Defaults([]byte("key")) + encrypter, decrypter := Defaults([]byte("key"), false) encrypted, err = Encrypt(msg, encrypter) require.NoError(t, err) decrypted, err = Decrypt(encrypted, decrypter) @@ -69,3 +70,75 @@ func TestHumanReadable(t *testing.T) { _, err = ParseHumanReadableKey(keyString + "=") require.Error(t, err) } + +type bothCrypter interface { + Decrypter + Encrypter +} + +func TestMultiDecryptor(t *testing.T) { + crypters := []bothCrypter{ + noopCrypter{}, + NewNACLSecretbox([]byte("key1")), + NewNACLSecretbox([]byte("key2")), + NewNACLSecretbox([]byte("key3")), + NewFernet([]byte("key1")), + NewFernet([]byte("key2")), + } + m := NewMultiDecrypter( + crypters[0], crypters[1], crypters[2], crypters[4], + NewMultiDecrypter(crypters[3], crypters[5]), + ) + + for i, c := range crypters { + plaintext := []byte(fmt.Sprintf("message %d", i)) + ciphertext, err := Encrypt(plaintext, c) + require.NoError(t, err) + decrypted, err := Decrypt(ciphertext, m) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) + + // for sanity, make sure the other crypters can't decrypt + for j, o := range crypters { + if j == i { + continue + } + _, err := Decrypt(ciphertext, o) + require.IsType(t, ErrCannotDecrypt{}, err) + } + } +} + +// The default encrypter/decrypter, if FIPS is not enabled, is NACLSecretBox. +// However, it can decrypt using all other supported algorithms. If FIPS is +// enabled, the encrypter/decrypter is Fernet only, because FIPS only permits +// (given the algorithms swarmkit supports) AES-128-CBC +func TestDefaults(t *testing.T) { + plaintext := []byte("my message") + + // encrypt something without FIPS enabled + c, d := Defaults([]byte("key"), false) + ciphertext, err := Encrypt(plaintext, c) + require.NoError(t, err) + decrypted, err := Decrypt(ciphertext, d) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) + + // with fips enabled, defaults should return a fernet encrypter + // and a decrypter that can't decrypt nacl + c, d = Defaults([]byte("key"), true) + _, err = Decrypt(ciphertext, d) + require.Error(t, err) + ciphertext, err = Encrypt(plaintext, c) + require.NoError(t, err) + decrypted, err = Decrypt(ciphertext, d) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) + + // without FIPS, and ensure we can decrypt the previous ciphertext + // (encrypted with fernet) with the decrypter returned by defaults + _, d = Defaults([]byte("key"), false) + decrypted, err = Decrypt(ciphertext, d) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) +} diff --git a/manager/encryption/fernet.go b/manager/encryption/fernet.go new file mode 100644 index 0000000000..d4d048b2e7 --- /dev/null +++ b/manager/encryption/fernet.go @@ -0,0 +1,54 @@ +package encryption + +import ( + "fmt" + + "github.com/docker/swarmkit/api" + + "github.com/fernet/fernet-go" +) + +// Fernet wraps the `fernet` library as an implementation of encrypter/decrypter. +type Fernet struct { + key fernet.Key +} + +// NewFernet returns a new Fernet encrypter/decrypter with the given key +func NewFernet(key []byte) Fernet { + frnt := Fernet{} + copy(frnt.key[:], key) + return frnt +} + +// Algorithm returns the type of algorithm this is (Fernet, which uses AES128-CBC) +func (f Fernet) Algorithm() api.MaybeEncryptedRecord_Algorithm { + return api.MaybeEncryptedRecord_FernetAES128CBC +} + +// Encrypt encrypts some bytes and returns an encrypted record +func (f Fernet) Encrypt(data []byte) (*api.MaybeEncryptedRecord, error) { + out, err := fernet.EncryptAndSign(data, &f.key) + if err != nil { + return nil, err + } + // fernet generates its own IVs, so nonce is empty + return &api.MaybeEncryptedRecord{ + Algorithm: f.Algorithm(), + Data: out, + }, nil +} + +// Decrypt decrypts a MaybeEncryptedRecord and returns some bytes +func (f Fernet) Decrypt(record api.MaybeEncryptedRecord) ([]byte, error) { + if record.Algorithm != f.Algorithm() { + return nil, fmt.Errorf("record is not a Fernet message") + } + + // -1 skips the TTL check, since we don't care about message expiry + out := fernet.VerifyAndDecrypt(record.Data, -1, []*fernet.Key{&f.key}) + // VerifyandDecrypt returns a nil message if it can't be verified and decrypted + if out == nil { + return nil, fmt.Errorf("decryption error using Fernet") + } + return out, nil +} diff --git a/manager/encryption/fernet_test.go b/manager/encryption/fernet_test.go new file mode 100644 index 0000000000..be29a6e194 --- /dev/null +++ b/manager/encryption/fernet_test.go @@ -0,0 +1,77 @@ +package encryption + +import ( + cryptorand "crypto/rand" + "io" + "testing" + + "github.com/docker/swarmkit/api" + "github.com/stretchr/testify/require" +) + +// Using the same key to encrypt the same message, this encrypter produces two +// different ciphertexts because the underlying algorithm uses different IVs. +// Both of these can be decrypted into the same data though. +func TestFernet(t *testing.T) { + key := make([]byte, 32) + _, err := io.ReadFull(cryptorand.Reader, key) + require.NoError(t, err) + keyCopy := make([]byte, 32) + copy(key, keyCopy) + + crypter1 := NewFernet(key) + crypter2 := NewFernet(keyCopy) + data := []byte("Hello again world") + + er1, err := crypter1.Encrypt(data) + require.NoError(t, err) + + er2, err := crypter2.Encrypt(data) + require.NoError(t, err) + + require.NotEqual(t, er1.Data, er2.Data) + require.Empty(t, er1.Nonce) + require.Empty(t, er2.Nonce) + + // it doesn't matter what the nonce is, it's ignored + _, err = io.ReadFull(cryptorand.Reader, er1.Nonce) + require.NoError(t, err) + + // both crypters can decrypt the other's text + for i, decrypter := range []Decrypter{crypter1, crypter2} { + for j, record := range []*api.MaybeEncryptedRecord{er1, er2} { + result, err := decrypter.Decrypt(*record) + require.NoError(t, err, "error decrypting ciphertext produced by cryptor %d using cryptor %d", j+1, i+1) + require.Equal(t, data, result) + } + } +} + +func TestFernetInvalidAlgorithm(t *testing.T) { + key := make([]byte, 32) + _, err := io.ReadFull(cryptorand.Reader, key) + require.NoError(t, err) + + crypter := NewFernet(key) + er, err := crypter.Encrypt([]byte("Hello again world")) + require.NoError(t, err) + er.Algorithm = api.MaybeEncryptedRecord_NotEncrypted + + _, err = crypter.Decrypt(*er) + require.Error(t, err) + require.Contains(t, err.Error(), "not a Fernet message") +} + +func TestFernetCannotDecryptWithoutRightKey(t *testing.T) { + key := make([]byte, 32) + _, err := io.ReadFull(cryptorand.Reader, key) + require.NoError(t, err) + + crypter := NewFernet(key) + er, err := crypter.Encrypt([]byte("Hello again world")) + require.NoError(t, err) + + crypter = NewFernet([]byte{}) + _, err = crypter.Decrypt(*er) + require.Error(t, err) +} diff --git a/manager/encryption/nacl_test.go b/manager/encryption/nacl_test.go index da3f30d2ea..329046ed76 100644 --- a/manager/encryption/nacl_test.go +++ b/manager/encryption/nacl_test.go @@ -16,26 +16,31 @@ func TestNACLSecretbox(t *testing.T) { key := make([]byte, 32) _, err := io.ReadFull(cryptorand.Reader, key) require.NoError(t, err) + keyCopy := make([]byte, 32) + copy(key, keyCopy) - crypter := NewNACLSecretbox(key) + crypter1 := NewNACLSecretbox(key) + crypter2 := NewNACLSecretbox(keyCopy) data := []byte("Hello again world") - er1, err := crypter.Encrypt(data) + er1, err := crypter1.Encrypt(data) require.NoError(t, err) - er2, err := crypter.Encrypt(data) + er2, err := crypter1.Encrypt(data) require.NoError(t, err) require.NotEqual(t, er1.Data, er2.Data) - require.NotEmpty(t, er1.Nonce, er2.Nonce) - - result, err := crypter.Decrypt(*er1) - require.NoError(t, err) - require.Equal(t, data, result) - - result, err = crypter.Decrypt(*er2) - require.NoError(t, err) - require.Equal(t, data, result) + require.NotEmpty(t, er1.Nonce) + require.NotEmpty(t, er2.Nonce) + + // both crypters can decrypt the other's text + for _, decrypter := range []Decrypter{crypter1, crypter2} { + for _, record := range []*api.MaybeEncryptedRecord{er1, er2} { + result, err := decrypter.Decrypt(*record) + require.NoError(t, err) + require.Equal(t, data, result) + } + } } func TestNACLSecretboxInvalidAlgorithm(t *testing.T) { diff --git a/manager/manager.go b/manager/manager.go index 84716b3217..2c6a0d3dcc 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -2,7 +2,6 @@ package manager import ( "crypto/tls" - "encoding/pem" "fmt" "net" "os" @@ -17,7 +16,6 @@ import ( gmetrics "github.com/docker/go-metrics" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" - "github.com/docker/swarmkit/ca/keyutils" "github.com/docker/swarmkit/connectionbroker" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/log" @@ -123,6 +121,11 @@ type Config struct { // PluginGetter provides access to docker's plugin inventory. PluginGetter plugingetter.PluginGetter + + // FIPS is a boolean stating whether the node is FIPS enabled - if this is the + // first node in the cluster, this setting is used to set the cluster-wide mandatory + // FIPS setting. + FIPS bool } // Manager is the cluster manager for Swarm. @@ -210,7 +213,7 @@ func New(config *Config) (*Manager, error) { raftCfg.HeartbeatTick = int(config.HeartbeatTick) } - dekRotator, err := NewRaftDEKManager(config.SecurityConfig.KeyWriter()) + dekRotator, err := NewRaftDEKManager(config.SecurityConfig.KeyWriter(), config.FIPS) if err != nil { return nil, err } @@ -224,6 +227,7 @@ func New(config *Config) (*Manager, error) { ForceNewCluster: config.ForceNewCluster, TLSCredentials: config.SecurityConfig.ClientTLSCreds, KeyRotator: dekRotator, + FIPS: config.FIPS, } raftNode := raft.NewNode(newNodeOpts) @@ -768,109 +772,6 @@ func (m *Manager) watchForClusterChanges(ctx context.Context) error { return nil } -// rotateRootCAKEK will attempt to rotate the key-encryption-key for root CA key-material in raft. -// If there is no passphrase set in ENV, it returns. -// If there is plain-text root key-material, and a passphrase set, it encrypts it. -// If there is encrypted root key-material and it is using the current passphrase, it returns. -// If there is encrypted root key-material, and it is using the previous passphrase, it -// re-encrypts it with the current passphrase. -func (m *Manager) rotateRootCAKEK(ctx context.Context, clusterID string) error { - // If we don't have a KEK, we won't ever be rotating anything - strPassphrase := os.Getenv(ca.PassphraseENVVar) - strPassphrasePrev := os.Getenv(ca.PassphraseENVVarPrev) - if strPassphrase == "" && strPassphrasePrev == "" { - return nil - } - if strPassphrase != "" { - log.G(ctx).Warn("Encrypting the root CA key in swarm using environment variables is deprecated. " + - "Support for decrypting or rotating the key will be removed in the future.") - } - - passphrase := []byte(strPassphrase) - passphrasePrev := []byte(strPassphrasePrev) - - s := m.raftNode.MemoryStore() - var ( - cluster *api.Cluster - err error - finalKey []byte - ) - // Retrieve the cluster identified by ClusterID - return s.Update(func(tx store.Tx) error { - cluster = store.GetCluster(tx, clusterID) - if cluster == nil { - return fmt.Errorf("cluster not found: %s", clusterID) - } - - // Try to get the private key from the cluster - privKeyPEM := cluster.RootCA.CAKey - if len(privKeyPEM) == 0 { - // We have no PEM root private key in this cluster. - log.G(ctx).Warnf("cluster %s does not have private key material", clusterID) - return nil - } - - // Decode the PEM private key - keyBlock, _ := pem.Decode(privKeyPEM) - if keyBlock == nil { - return fmt.Errorf("invalid PEM-encoded private key inside of cluster %s", clusterID) - } - - if keyutils.IsEncryptedPEMBlock(keyBlock) { - // PEM encryption does not have a digest, so sometimes decryption doesn't - // error even with the wrong passphrase. So actually try to parse it into a valid key. - _, err := keyutils.ParsePrivateKeyPEMWithPassword(privKeyPEM, []byte(passphrase)) - if err == nil { - // This key is already correctly encrypted with the correct KEK, nothing to do here - return nil - } - - // This key is already encrypted, but failed with current main passphrase. - // Let's try to decrypt with the previous passphrase, and parse into a valid key, for the - // same reason as above. - _, err = keyutils.ParsePrivateKeyPEMWithPassword(privKeyPEM, []byte(passphrasePrev)) - if err != nil { - // We were not able to decrypt either with the main or backup passphrase, error - return err - } - // ok the above passphrase is correct, so decrypt the PEM block so we can re-encrypt - - // since the key was successfully decrypted above, there will be no error doing PEM - // decryption - unencryptedDER, _ := keyutils.DecryptPEMBlock(keyBlock, []byte(passphrasePrev)) - unencryptedKeyBlock := &pem.Block{ - Type: keyBlock.Type, - Bytes: unencryptedDER, - } - - // we were able to decrypt the key with the previous passphrase - if the current passphrase is empty, - // the we store the decrypted key in raft - finalKey = pem.EncodeToMemory(unencryptedKeyBlock) - - // the current passphrase is not empty, so let's encrypt with the new one and store it in raft - if strPassphrase != "" { - finalKey, err = ca.EncryptECPrivateKey(finalKey, strPassphrase) - if err != nil { - log.G(ctx).WithError(err).Debugf("failed to rotate the key-encrypting-key for the root key material of cluster %s", clusterID) - return err - } - } - } else if strPassphrase != "" { - // If this key is not encrypted, and the passphrase is not nil, then we have to encrypt it - finalKey, err = ca.EncryptECPrivateKey(privKeyPEM, strPassphrase) - if err != nil { - log.G(ctx).WithError(err).Debugf("failed to rotate the key-encrypting-key for the root key material of cluster %s", clusterID) - return err - } - } else { - return nil // don't update if it's not encrypted and we don't want it encrypted - } - - log.G(ctx).Infof("Updating the encryption on the root key material of cluster %s", clusterID) - cluster.RootCA.CAKey = finalKey - return store.UpdateCluster(tx, cluster) - }) -} - // handleLeadershipEvents handles the is leader event or is follower event. func (m *Manager) handleLeadershipEvents(ctx context.Context, leadershipCh chan events.Event) { for { @@ -938,7 +839,10 @@ func (m *Manager) becomeLeader(ctx context.Context) { initialCAConfig := ca.DefaultCAConfig() initialCAConfig.ExternalCAs = m.config.ExternalCAs - var unlockKeys []*api.EncryptionKey + var ( + unlockKeys []*api.EncryptionKey + err error + ) if m.config.AutoLockManagers { unlockKeys = []*api.EncryptionKey{{ Subsystem: ca.ManagerRole, @@ -957,7 +861,8 @@ func (m *Manager) becomeLeader(ctx context.Context) { raftCfg, api.EncryptionConfig{AutoLockManagers: m.config.AutoLockManagers}, unlockKeys, - rootCA)) + rootCA, + m.config.FIPS)) if err != nil && err != store.ErrExist { log.G(ctx).WithError(err).Errorf("error creating cluster object") @@ -991,12 +896,6 @@ func (m *Manager) becomeLeader(ctx context.Context) { return nil }) - // Attempt to rotate the key-encrypting-key of the root CA key-material - err := m.rotateRootCAKEK(ctx, clusterID) - if err != nil { - log.G(ctx).WithError(err).Error("root key-encrypting-key rotation failed") - } - m.replicatedOrchestrator = replicated.NewReplicatedOrchestrator(s) m.constraintEnforcer = constraintenforcer.New(s) m.globalOrchestrator = global.NewGlobalOrchestrator(s) @@ -1124,7 +1023,8 @@ func defaultClusterObject( raftCfg api.RaftConfig, encryptionConfig api.EncryptionConfig, initialUnlockKeys []*api.EncryptionKey, - rootCA *ca.RootCA) *api.Cluster { + rootCA *ca.RootCA, + fips bool) *api.Cluster { var caKey []byte if rcaSigner, err := rootCA.Signer(); err == nil { caKey = rcaSigner.Key @@ -1151,11 +1051,12 @@ func defaultClusterObject( CACert: rootCA.Certs, CACertHash: rootCA.Digest.String(), JoinTokens: api.JoinTokens{ - Worker: ca.GenerateJoinToken(rootCA), - Manager: ca.GenerateJoinToken(rootCA), + Worker: ca.GenerateJoinToken(rootCA, fips), + Manager: ca.GenerateJoinToken(rootCA, fips), }, }, UnlockKeys: initialUnlockKeys, + FIPS: fips, } } diff --git a/manager/manager_test.go b/manager/manager_test.go index e22fcafd4c..f55b7f38a8 100644 --- a/manager/manager_test.go +++ b/manager/manager_test.go @@ -296,7 +296,7 @@ func TestManagerLockUnlock(t *testing.T) { require.NotNil(t, keyBlock) require.False(t, keyutils.IsEncryptedPEMBlock(keyBlock)) require.Len(t, keyBlock.Headers, 2) - currentDEK, err := decodePEMHeaderValue(keyBlock.Headers[pemHeaderRaftDEK], nil) + currentDEK, err := decodePEMHeaderValue(keyBlock.Headers[pemHeaderRaftDEK], nil, false) require.NoError(t, err) require.NotEmpty(t, currentDEK) @@ -349,7 +349,7 @@ func TestManagerLockUnlock(t *testing.T) { // a little bit, and is best effort only currentDEKString, ok := keyBlock.Headers[pemHeaderRaftDEK] require.True(t, ok) // there should never NOT be a current header - nowCurrentDEK, err := decodePEMHeaderValue(currentDEKString, unlockKeyResp.UnlockKey) + nowCurrentDEK, err := decodePEMHeaderValue(currentDEKString, unlockKeyResp.UnlockKey, false) require.NoError(t, err) // it should always be encrypted if bytes.Equal(currentDEK, nowCurrentDEK) { return fmt.Errorf("snapshot has not been finished yet") @@ -366,7 +366,7 @@ func TestManagerLockUnlock(t *testing.T) { require.False(t, ok) // verify that the snapshot is readable with the new DEK - encrypter, decrypter := encryption.Defaults(currentDEK) + encrypter, decrypter := encryption.Defaults(currentDEK, false) // we can't use the raftLogger, because the WALs are still locked while the raft node is up. And once we remove // the manager, they'll be deleted. snapshot, err := storage.NewSnapFactory(encrypter, decrypter).New(filepath.Join(stateDir, "raft", "snap-v3-encrypted")).Load() @@ -422,8 +422,13 @@ func TestManagerLockUnlock(t *testing.T) { return nil }, 1*time.Second)) - // the DEK should not have been rotated, just decrypted (which was tested previously) - unencryptedDEK, err := decodePEMHeaderValue(keyBlock.Headers[pemHeaderRaftDEK], nil) + // the new key should not be encrypted, and the DEK should also be unencrypted + // but not rotated + keyBlock, _ = pem.Decode(unlockedKey) + require.NotNil(t, keyBlock) + require.False(t, keyutils.IsEncryptedPEMBlock(keyBlock)) + + unencryptedDEK, err := decodePEMHeaderValue(keyBlock.Headers[pemHeaderRaftDEK], nil, false) require.NoError(t, err) require.NotNil(t, unencryptedDEK) require.Equal(t, currentDEK, unencryptedDEK) @@ -435,158 +440,3 @@ func TestManagerLockUnlock(t *testing.T) { // error. <-done } - -// Tests manager rotates encryption of root key data in the raft store -func TestManagerEncryptsDecryptsRootKeyMaterial(t *testing.T) { - tc := cautils.NewTestCA(t) - defer tc.Stop() - - temp, err := ioutil.TempFile("", "test-socket") - require.NoError(t, err) - require.NoError(t, temp.Close()) - require.NoError(t, os.Remove(temp.Name())) - - defer os.RemoveAll(temp.Name()) - - stateDir, err := ioutil.TempDir("", "test-raft") - require.NoError(t, err) - defer os.RemoveAll(stateDir) - - managerSecurityConfig, err := tc.NewNodeConfig(ca.ManagerRole) - require.NoError(t, err) - - _, _, err = managerSecurityConfig.KeyReader().Read() - require.NoError(t, err) - - config := Config{ - RemoteAPI: &RemoteAddrs{ListenAddr: "127.0.0.1:0"}, - ControlAPI: temp.Name(), - StateDir: stateDir, - SecurityConfig: managerSecurityConfig, - RootCAPaths: tc.Paths.RootCA, - } - done := make(chan error) - defer close(done) - - var m *Manager - startManager := func() { - m, err = New(&config) - require.NoError(t, err) - require.NotNil(t, m) - - go func() { - done <- m.Run(tc.Context) - }() - } - - startManager() - - var clusterID string - // wait for cluster data to be there - err = testutils.PollFunc(nil, func() error { - // using store.Update just because it returns an error, as opposed to store.View - return m.raftNode.MemoryStore().Update(func(tx store.Tx) error { - clusters, err := store.FindClusters(tx, store.All) - if err != nil { - return err - } - if len(clusters) != 1 { - return fmt.Errorf("expected 1 cluster, got %d", len(clusters)) - } - clusterID = clusters[0].ID - return nil - }) - }) - - os.Setenv(ca.PassphraseENVVar, "kek") - defer os.Unsetenv(ca.PassphraseENVVar) - - // restart - m.Stop(tc.Context, false) - <-done - startManager() - - // wait for the key to be encrypted in the raft store - err = testutils.PollFunc(nil, func() error { - return m.raftNode.MemoryStore().Update(func(tx store.Tx) error { - cluster := store.GetCluster(tx, clusterID) - if cluster == nil { - return fmt.Errorf("cluster gone") - } - keyBlock, _ := pem.Decode(cluster.RootCA.CAKey) - if keyBlock == nil { - return fmt.Errorf("could not pem decode root key") - } - if !keyutils.IsEncryptedPEMBlock(keyBlock) { - return fmt.Errorf("root key material not encrypted yet") - } - _, err = keyutils.DecryptPEMBlock(keyBlock, []byte("kek")) - return err - }) - }) - require.NoError(t, err) - - os.Unsetenv(ca.PassphraseENVVar) - os.Setenv(ca.PassphraseENVVarPrev, "kek") - defer os.Unsetenv(ca.PassphraseENVVarPrev) - - // restart - m.Stop(tc.Context, false) - <-done - startManager() - - // wait for the key to be decrypted in the raft store - pollDecrypted := func() error { - return testutils.PollFunc(nil, func() error { - // wait until we are leader first, because otherwise the raft node could still be catching - // up on all the logs on disk and hence not have processed the "encrypt CA key" log yet - if !m.raftNode.IsLeader() { - return fmt.Errorf("node is not leader yet") - } - return m.raftNode.MemoryStore().Update(func(tx store.Tx) error { - cluster := store.GetCluster(tx, clusterID) - if cluster == nil { - return fmt.Errorf("cluster gone") - } - keyBlock, _ := pem.Decode(cluster.RootCA.CAKey) - if keyBlock == nil { - return fmt.Errorf("could not pem decode root key") - } - if keyutils.IsEncryptedPEMBlock(keyBlock) { - return fmt.Errorf("root key material not decrypted yet") - } - return nil - }) - }) - } - require.NoError(t, pollDecrypted()) - - // update the key to that can be "decrypted" with both "" and "kek" as the password. This - // doesn't actually match the root CA certificate, and hence the security config can't be - // updated, but we're just checking that the CA key is decrypted. - require.NoError(t, m.raftNode.MemoryStore().Update(func(tx store.Tx) error { - cluster := store.GetCluster(tx, clusterID) - if cluster == nil { - return fmt.Errorf("cluster gone") - } - cluster.RootCA.CAKey = []byte(` ------BEGIN ENCRYPTED PRIVATE KEY----- -MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAiLGJtiTmJ3rQICCAAw -HQYJYIZIAWUDBAEqBBBeDoliB0Qe73DdcMeFCuRzBIGQP/iFMPj9BJ/81GV//fMp -KPozbY0EWodXt7KArbeROd5+uWw1muLANUa3KkkXyQhmzlR2Zv3Y/kBuPay9RweU -md94ZD/HY9K+ISv4tIA7u8gp2Hqr0elfG0QqBuwrh688ZF5jii6umZzXtLVVMvWd -NF7w1CA6b8w1aTIklVjv0AJ9tgtGQb9phVigPAdyyw6v ------END ENCRYPTED PRIVATE KEY----- -`) - return store.UpdateCluster(tx, cluster) - })) - - // restart - m.Stop(tc.Context, false) - <-done - startManager() - require.NoError(t, pollDecrypted()) - - m.Stop(tc.Context, false) - <-done -} diff --git a/manager/state/raft/raft.go b/manager/state/raft/raft.go index 56b7c7c966..9eec8d4dfb 100644 --- a/manager/state/raft/raft.go +++ b/manager/state/raft/raft.go @@ -192,6 +192,9 @@ type NodeOptions struct { // DisableStackDump prevents Run from dumping goroutine stacks when the // store becomes stuck. DisableStackDump bool + + // FIPS specifies whether the raft encryption should be FIPS compliant + FIPS bool } func init() { diff --git a/manager/state/raft/storage.go b/manager/state/raft/storage.go index f538317c06..547b775645 100644 --- a/manager/state/raft/storage.go +++ b/manager/state/raft/storage.go @@ -34,6 +34,7 @@ func (n *Node) readFromDisk(ctx context.Context) (*raftpb.Snapshot, storage.WALD n.raftLogger = &storage.EncryptedRaftLogger{ StateDir: n.opts.StateDir, EncryptionKey: keys.CurrentDEK, + FIPS: n.opts.FIPS, } if keys.PendingDEK != nil { n.raftLogger.EncryptionKey = keys.PendingDEK diff --git a/manager/state/raft/storage/storage.go b/manager/state/raft/storage/storage.go index 539e05d5ce..bbd262f37c 100644 --- a/manager/state/raft/storage/storage.go +++ b/manager/state/raft/storage/storage.go @@ -13,7 +13,6 @@ import ( "github.com/coreos/etcd/snap" "github.com/coreos/etcd/wal" "github.com/coreos/etcd/wal/walpb" - "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/log" "github.com/docker/swarmkit/manager/encryption" "github.com/pkg/errors" @@ -34,25 +33,14 @@ var versionedWALSnapDirs = []walSnapDirs{ {wal: "wal", snap: "snap"}, } -// MultiDecrypter attempts to decrypt with a list of decrypters -type MultiDecrypter []encryption.Decrypter - -// Decrypt tries to decrypt using all the decrypters -func (m MultiDecrypter) Decrypt(r api.MaybeEncryptedRecord) (result []byte, err error) { - for _, d := range m { - result, err = d.Decrypt(r) - if err == nil { - return - } - } - return -} - // EncryptedRaftLogger saves raft data to disk type EncryptedRaftLogger struct { StateDir string EncryptionKey []byte + // FIPS specifies whether the encryption should be FIPS-compliant + FIPS bool + // mutex is locked for writing only when we need to replace the wal object and snapshotter // object, not when we're writing snapshots or wals (in which case it's locked for reading) encoderMu sync.RWMutex @@ -68,14 +56,14 @@ func (e *EncryptedRaftLogger) BootstrapFromDisk(ctx context.Context, oldEncrypti walDir := e.walDir() snapDir := e.snapDir() - encrypter, decrypter := encryption.Defaults(e.EncryptionKey) + encrypter, decrypter := encryption.Defaults(e.EncryptionKey, e.FIPS) if oldEncryptionKeys != nil { decrypters := []encryption.Decrypter{decrypter} for _, key := range oldEncryptionKeys { - _, d := encryption.Defaults(key) + _, d := encryption.Defaults(key, e.FIPS) decrypters = append(decrypters, d) } - decrypter = MultiDecrypter(decrypters) + decrypter = encryption.NewMultiDecrypter(decrypters...) } snapFactory := NewSnapFactory(encrypter, decrypter) @@ -156,7 +144,7 @@ func (e *EncryptedRaftLogger) BootstrapFromDisk(ctx context.Context, oldEncrypti func (e *EncryptedRaftLogger) BootstrapNew(metadata []byte) error { e.encoderMu.Lock() defer e.encoderMu.Unlock() - encrypter, decrypter := encryption.Defaults(e.EncryptionKey) + encrypter, decrypter := encryption.Defaults(e.EncryptionKey, e.FIPS) walFactory := NewWALFactory(encrypter, decrypter) for _, dirpath := range []string{filepath.Dir(e.walDir()), e.snapDir()} { @@ -199,7 +187,7 @@ func (e *EncryptedRaftLogger) RotateEncryptionKey(newKey []byte) { panic(fmt.Errorf("EncryptedRaftLogger's WAL is not a wrappedWAL")) } - wrapped.encrypter, wrapped.decrypter = encryption.Defaults(newKey) + wrapped.encrypter, wrapped.decrypter = encryption.Defaults(newKey, e.FIPS) e.snapshotter = NewSnapFactory(wrapped.encrypter, wrapped.decrypter).New(e.snapDir()) } diff --git a/manager/state/raft/storage/storage_test.go b/manager/state/raft/storage/storage_test.go index 89db461578..f192eb48c2 100644 --- a/manager/state/raft/storage/storage_test.go +++ b/manager/state/raft/storage/storage_test.go @@ -184,7 +184,7 @@ func TestMigrateToV3EncryptedForm(t *testing.T) { v3EncryptedSnapshot.Metadata.Index += 200 v3EncryptedSnapshot.Metadata.Term += 20 - encoder, decoders := encryption.Defaults(dek) + encoder, decoders := encryption.Defaults(dek, false) walFactory := NewWALFactory(encoder, decoders) snapFactory := NewSnapFactory(encoder, decoders) diff --git a/node/node.go b/node/node.go index d7b6011207..0886ea9659 100644 --- a/node/node.go +++ b/node/node.go @@ -14,6 +14,9 @@ import ( "sync" "time" + "github.com/docker/swarmkit/ca/keyutils" + "github.com/docker/swarmkit/identity" + "github.com/boltdb/bolt" "github.com/docker/docker/pkg/plugingetter" metrics "github.com/docker/go-metrics" @@ -52,6 +55,9 @@ var ( // ErrInvalidUnlockKey is returned when we can't decrypt the TLS certificate ErrInvalidUnlockKey = errors.New("node is locked, and needs a valid unlock key") + + // ErrMandatoryFIPS is returned when the cluster we are joining mandates FIPS, but we are running in non-FIPS mode + ErrMandatoryFIPS = errors.New("node is not FIPS-enabled but cluster requires FIPS") ) func init() { @@ -123,6 +129,9 @@ type Config struct { // PluginGetter provides access to docker's plugin inventory. PluginGetter plugingetter.PluginGetter + + // FIPS is a boolean stating whether the node is FIPS enabled + FIPS bool } // Node implements the primary node functionality for a member of a swarm @@ -523,6 +532,7 @@ waitPeer: CertIssuerPublicKey: issuer.PublicKey, CertIssuerSubject: issuer.Subject, }, + FIPS: n.config.FIPS, } // if a join address has been specified, then if the agent fails to connect due to a TLS error, fail fast - don't // keep re-trying to join @@ -660,13 +670,36 @@ func (n *Node) Remotes() []api.Peer { return remotes } +// Given a cluster ID, returns whether the cluster ID indicates that the cluster +// mandates FIPS mode. These cluster IDs start with "FIPS." as a prefix. +func isMandatoryFIPSClusterID(securityConfig *ca.SecurityConfig) bool { + return strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.") +} + +// Given a join token, returns whether it indicates that the cluster mandates FIPS +// mode. +func isMandatoryFIPSClusterJoinToken(joinToken string) bool { + if parsed, err := ca.ParseJoinToken(joinToken); err == nil { + return parsed.FIPS + } + return false +} + +func generateFIPSClusterID() string { + return "FIPS." + identity.NewID() +} + func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigPaths) (*ca.SecurityConfig, func() error, error) { var ( securityConfig *ca.SecurityConfig cancel func() error ) - krw := ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{}) + krw := ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{FIPS: n.config.FIPS}) + // if FIPS is required, we want to make sure our key is stored in PKCS8 format + if n.config.FIPS { + krw.SetKeyFormatter(keyutils.FIPS) + } if err := krw.Migrate(); err != nil { return nil, nil, err } @@ -696,7 +729,7 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP if n.config.AutoLockManagers { n.unlockKey = encryption.GenerateSecretKey() } - krw = ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{}) + krw = ca.NewKeyReadWriter(paths.Node, n.unlockKey, &manager.RaftDEKData{FIPS: n.config.FIPS}) rootCA, err = ca.CreateRootCA(ca.DefaultRootCN) if err != nil { return nil, nil, err @@ -706,6 +739,10 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP } log.G(ctx).Debug("generated CA key and certificate") } else if err == ca.ErrNoLocalRootCA { // from previous error loading the root CA from disk + // if we are attempting to join another cluster, which has a FIPS join token, and we are not FIPS, error + if n.config.JoinAddr != "" && isMandatoryFIPSClusterJoinToken(n.config.JoinToken) && !n.config.FIPS { + return nil, nil, ErrMandatoryFIPS + } rootCA, err = ca.DownloadRootCA(ctx, paths.RootCA, n.config.JoinToken, n.connBroker) if err != nil { return nil, nil, err @@ -730,11 +767,21 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP } log.G(ctx).WithError(err).Debugf("no node credentials found in: %s", krw.Target()) - securityConfig, cancel, err = rootCA.CreateSecurityConfig(ctx, krw, ca.CertificateRequestConfig{ + // if we are attempting to join another cluster, which has a FIPS join token, and we are not FIPS, error + if n.config.JoinAddr != "" && isMandatoryFIPSClusterJoinToken(n.config.JoinToken) && !n.config.FIPS { + return nil, nil, ErrMandatoryFIPS + } + + requestConfig := ca.CertificateRequestConfig{ Token: n.config.JoinToken, Availability: n.config.Availability, ConnBroker: n.connBroker, - }) + } + // If this is a new cluster, we want to name the cluster ID "FIPS-something" + if n.config.FIPS { + requestConfig.Organization = generateFIPSClusterID() + } + securityConfig, cancel, err = rootCA.CreateSecurityConfig(ctx, krw, requestConfig) if err != nil { return nil, nil, err @@ -742,6 +789,10 @@ func (n *Node) loadSecurityConfig(ctx context.Context, paths *ca.SecurityConfigP } } + if isMandatoryFIPSClusterID(securityConfig) && !n.config.FIPS { + return nil, nil, ErrMandatoryFIPS + } + n.Lock() n.role = securityConfig.ClientTLSCreds.Role() n.nodeID = securityConfig.ClientTLSCreds.NodeID() @@ -847,6 +898,7 @@ func (n *Node) runManager(ctx context.Context, securityConfig *ca.SecurityConfig Availability: n.config.Availability, PluginGetter: n.config.PluginGetter, RootCAPaths: rootPaths, + FIPS: n.config.FIPS, }) if err != nil { return false, err diff --git a/node/node_test.go b/node/node_test.go index 709b0d3d98..ebd44b03e8 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "testing" "time" @@ -16,8 +17,10 @@ import ( agentutils "github.com/docker/swarmkit/agent/testutils" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" + "github.com/docker/swarmkit/ca/keyutils" cautils "github.com/docker/swarmkit/ca/testutils" "github.com/docker/swarmkit/identity" + "github.com/docker/swarmkit/log" "github.com/docker/swarmkit/manager/state/store" "github.com/docker/swarmkit/testutils" "github.com/pkg/errors" @@ -25,6 +28,10 @@ import ( "golang.org/x/net/context" ) +func getLoggingContext(t *testing.T) context.Context { + return log.WithLogger(context.Background(), log.L.WithField("test", t.Name())) +} + // If there is nothing on disk and no join addr, we create a new CA and a new set of TLS certs. // If AutoLockManagers is enabled, the TLS key is encrypted with a randomly generated lock key. func TestLoadSecurityConfigNewNode(t *testing.T) { @@ -148,9 +155,9 @@ func TestLoadSecurityConfigLoadFromDisk(t *testing.T) { require.Equal(t, ErrInvalidUnlockKey, err) // Invalid CA - rootCA, err = ca.CreateRootCA(ca.DefaultRootCN) + otherRootCA, err := ca.CreateRootCA(ca.DefaultRootCN) require.NoError(t, err) - require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA)) + require.NoError(t, ca.SaveRootCA(otherRootCA, paths.RootCA)) node, err = New(&Config{ StateDir: tempdir, JoinAddr: peer.Addr, @@ -160,6 +167,21 @@ func TestLoadSecurityConfigLoadFromDisk(t *testing.T) { require.NoError(t, err) _, _, err = node.loadSecurityConfig(context.Background(), paths) require.IsType(t, x509.UnknownAuthorityError{}, errors.Cause(err)) + + // Convert to PKCS1 and require FIPS + require.NoError(t, krw.DowngradeKey()) + // go back to the previous root CA + require.NoError(t, ca.SaveRootCA(rootCA, paths.RootCA)) + node, err = New(&Config{ + StateDir: tempdir, + JoinAddr: peer.Addr, + JoinToken: tc.ManagerToken, + UnlockKey: []byte("passphrase"), + FIPS: true, + }) + require.NoError(t, err) + _, _, err = node.loadSecurityConfig(context.Background(), paths) + require.Equal(t, keyutils.ErrFIPSUnsupportedKeyFormat, errors.Cause(err)) } // If there is no CA, and a join addr is provided, one is downloaded from the @@ -254,6 +276,162 @@ func TestLoadSecurityConfigDownloadAllCerts(t *testing.T) { require.NoError(t, err) } +// If there is nothing on disk and no join addr, and FIPS is enabled, we create a cluster whose +// ID starts with 'FIPS.' +func TestLoadSecurityConfigNodeFIPSCreateCluster(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-security-config-fips-new-cluster") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + paths := ca.NewConfigPaths(filepath.Join(tempdir, "certificates")) + + tc := cautils.NewTestCA(t) + defer tc.Stop() + + config := &Config{ + StateDir: tempdir, + FIPS: true, + } + + node, err := New(config) + require.NoError(t, err) + securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths) + require.NoError(t, err) + defer cancel() + require.NotNil(t, securityConfig) + require.True(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.")) +} + +// If FIPS is enabled and there is a join address, the cluster ID is whatever the CA set +// the cluster ID to. +func TestLoadSecurityConfigNodeFIPSJoinCluster(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-security-config-fips-join-cluster") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + certDir := filepath.Join(tempdir, "certificates") + paths := ca.NewConfigPaths(certDir) + + for _, fips := range []bool{true, false} { + require.NoError(t, os.RemoveAll(certDir)) + + var tc *cautils.TestCA + if fips { + tc = cautils.NewFIPSTestCA(t) + } else { + tc = cautils.NewTestCA(t) + } + defer tc.Stop() + + peer, err := tc.ConnBroker.Remotes().Select() + require.NoError(t, err) + + node, err := New(&Config{ + StateDir: tempdir, + JoinAddr: peer.Addr, + JoinToken: tc.ManagerToken, + FIPS: true, + }) + require.NoError(t, err) + securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths) + require.NoError(t, err) + defer cancel() + require.NotNil(t, securityConfig) + require.Equal(t, fips, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.")) + } +} + +// If the certificate specifies that the cluster requires FIPS mode, loading the security +// config will fail if the node is not FIPS enabled. +func TestLoadSecurityConfigRespectsFIPSCert(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-security-config-fips-cert-on-disk") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + tc := cautils.NewFIPSTestCA(t) + defer tc.Stop() + + certDir := filepath.Join(tempdir, "certificates") + require.NoError(t, os.Mkdir(certDir, 0700)) + paths := ca.NewConfigPaths(certDir) + + // copy certs and keys from the test CA using a hard link + _, err = tc.WriteNewNodeConfig(ca.ManagerRole) + require.NoError(t, err) + require.NoError(t, os.Link(tc.Paths.Node.Cert, paths.Node.Cert)) + require.NoError(t, os.Link(tc.Paths.Node.Key, paths.Node.Key)) + require.NoError(t, os.Link(tc.Paths.RootCA.Cert, paths.RootCA.Cert)) + + node, err := New(&Config{StateDir: tempdir}) + require.NoError(t, err) + _, _, err = node.loadSecurityConfig(tc.Context, paths) + require.Equal(t, ErrMandatoryFIPS, err) + + node, err = New(&Config{ + StateDir: tempdir, + FIPS: true, + }) + require.NoError(t, err) + securityConfig, cancel, err := node.loadSecurityConfig(tc.Context, paths) + require.NoError(t, err) + defer cancel() + require.NotNil(t, securityConfig) + require.True(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.")) +} + +// If FIPS is disabled and there is a join address and token, and the join token indicates +// the cluster requires fips, then loading the security config will fail. However, if +// there are already certs on disk, it will load them and ignore the join token. +func TestLoadSecurityConfigNonFIPSNodeJoinCluster(t *testing.T) { + tempdir, err := ioutil.TempDir("", "test-security-config-nonfips-join-cluster") + require.NoError(t, err) + defer os.RemoveAll(tempdir) + + certDir := filepath.Join(tempdir, "certificates") + require.NoError(t, os.Mkdir(certDir, 0700)) + paths := ca.NewConfigPaths(certDir) + + tc := cautils.NewTestCA(t) + defer tc.Stop() + // copy certs and keys from the test CA using a hard link + _, err = tc.WriteNewNodeConfig(ca.ManagerRole) + require.NoError(t, err) + require.NoError(t, os.Link(tc.Paths.Node.Cert, paths.Node.Cert)) + require.NoError(t, os.Link(tc.Paths.Node.Key, paths.Node.Key)) + require.NoError(t, os.Link(tc.Paths.RootCA.Cert, paths.RootCA.Cert)) + + tcFIPS := cautils.NewFIPSTestCA(t) + defer tcFIPS.Stop() + + peer, err := tcFIPS.ConnBroker.Remotes().Select() + require.NoError(t, err) + + node, err := New(&Config{ + StateDir: tempdir, + JoinAddr: peer.Addr, + JoinToken: tcFIPS.ManagerToken, + }) + require.NoError(t, err) + securityConfig, cancel, err := node.loadSecurityConfig(tcFIPS.Context, paths) + require.NoError(t, err) + defer cancel() + require.NotNil(t, securityConfig) + require.False(t, strings.HasPrefix(securityConfig.ClientTLSCreds.Organization(), "FIPS.")) + + // remove the node cert only - now that the node has to download the certs, it will check the + // join address and fail + require.NoError(t, os.Remove(paths.Node.Cert)) + + _, _, err = node.loadSecurityConfig(tcFIPS.Context, paths) + require.Equal(t, ErrMandatoryFIPS, err) + + // remove all the certs (CA and node) - the node will also check the join address and fail + require.NoError(t, os.RemoveAll(certDir)) + + _, _, err = node.loadSecurityConfig(tcFIPS.Context, paths) + require.Equal(t, ErrMandatoryFIPS, err) +} + func TestManagerRespectsDispatcherRootCAUpdate(t *testing.T) { tmpDir, err := ioutil.TempDir("", "manager-root-ca-update") require.NoError(t, err) @@ -488,3 +666,40 @@ func TestManagerFailedStartup(t *testing.T) { require.EqualError(t, node.err, "manager stopped: can't initialize raft node: attempted to join raft cluster without knowing own address") } } + +// TestFIPSConfiguration ensures that new keys will be stored in PKCS8 format. +func TestFIPSConfiguration(t *testing.T) { + ctx := getLoggingContext(t) + tmpDir, err := ioutil.TempDir("", "fips") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + paths := ca.NewConfigPaths(filepath.Join(tmpDir, "certificates")) + + // don't bother with a listening socket + cAddr := filepath.Join(tmpDir, "control.sock") + cfg := &Config{ + ListenControlAPI: cAddr, + StateDir: tmpDir, + Executor: &agentutils.TestExecutor{}, + FIPS: true, + } + node, err := New(cfg) + require.NoError(t, err) + require.NoError(t, node.Start(ctx)) + defer func() { + require.NoError(t, node.Stop(ctx)) + }() + + select { + case <-node.Ready(): + case <-time.After(5 * time.Second): + require.FailNow(t, "node did not ready in time") + } + + nodeKey, err := ioutil.ReadFile(paths.Node.Key) + require.NoError(t, err) + pemBlock, _ := pem.Decode(nodeKey) + require.NotNil(t, pemBlock) + require.True(t, keyutils.IsPKCS8(pemBlock.Bytes)) +} diff --git a/vendor.conf b/vendor.conf index 8949ea01fc..da010d405a 100644 --- a/vendor.conf +++ b/vendor.conf @@ -24,10 +24,10 @@ github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 github.com/docker/go-units 954fed01cc617c55d838fa2230073f2cb17386c8 github.com/docker/libkv 9fd56606e928ff1f309808f5d5a0b7a2ef73f9a8 -github.com/docker/libnetwork 21544598c53fa36a3c771a8725c643dd2340f845 +github.com/docker/libnetwork 21544598c53fa36a3c771a8725c643dd2340f845 github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a github.com/opencontainers/runc d40db12e72a40109dfcf28539f5ee0930d2f0277 -github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448 +github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448 github.com/opencontainers/image-spec v1.0.0 # containerd executor @@ -44,6 +44,7 @@ github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 github.com/boltdb/bolt e72f08ddb5a52992c0a44c7dda9316c7333938b2 github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a github.com/dustin/go-humanize 8929fe90cee4b2cb9deb468b51fb34eba64d1bf0 +github.com/fernet/fernet-go 1b2437bc582b3cfbb341ee5a29f8ef5b42912ff2 github.com/google/certificate-transparency 0f6e3d1d1ba4d03fdaab7cd716f36255c2e48341 github.com/hashicorp/go-immutable-radix 8e8ed81f8f0bf1bdd829593fdd5c29922c1ea990 github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad diff --git a/vendor/github.com/fernet/fernet-go/License b/vendor/github.com/fernet/fernet-go/License new file mode 100644 index 0000000000..14214fbe0f --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/License @@ -0,0 +1,20 @@ +Copyright © 2013 Keith Rarick + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/fernet/fernet-go/Readme b/vendor/github.com/fernet/fernet-go/Readme new file mode 100644 index 0000000000..50cc26cfd4 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/Readme @@ -0,0 +1,22 @@ +Fernet takes a user-provided *message* (an arbitrary sequence of +bytes), a *key* (256 bits), and the current time, and produces a +*token*, which contains the message in a form that can't be read +or altered without the key. + +This package is compatible with the other implementations at +https://github.com/fernet. They can exchange tokens freely among +each other. + +Documentation: http://godoc.org/github.com/fernet/fernet-go + + +INSTALL + + $ go get github.com/fernet/fernet-go + + +For more information and background, see the Fernet spec at +https://github.com/fernet/spec. + +Fernet is distributed under the terms of the MIT license. +See the License file for details. diff --git a/vendor/github.com/fernet/fernet-go/fernet.go b/vendor/github.com/fernet/fernet-go/fernet.go new file mode 100644 index 0000000000..8549e69c45 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/fernet.go @@ -0,0 +1,168 @@ +// Package fernet takes a user-provided message (an arbitrary +// sequence of bytes), a key (256 bits), and the current time, +// and produces a token, which contains the message in a form +// that can't be read or altered without the key. +// +// For more information and background, see the Fernet spec +// at https://github.com/fernet/spec. +// +// Subdirectories in this package provide command-line tools +// for working with Fernet keys and tokens. +package fernet + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "encoding/binary" + "io" + "time" +) + +const ( + version byte = 0x80 + tsOffset = 1 + ivOffset = tsOffset + 8 + payOffset = ivOffset + aes.BlockSize + overhead = 1 + 8 + aes.BlockSize + sha256.Size // ver + ts + iv + hmac + maxClockSkew = 60 * time.Second +) + +var encoding = base64.URLEncoding + +// generates a token from msg, writes it into tok, and returns the +// number of bytes generated, which is encodedLen(msg). +// len(tok) must be >= encodedLen(len(msg)) +func gen(tok, msg, iv []byte, ts time.Time, k *Key) int { + tok[0] = version + binary.BigEndian.PutUint64(tok[tsOffset:], uint64(ts.Unix())) + copy(tok[ivOffset:], iv) + p := tok[payOffset:] + n := pad(p, msg, aes.BlockSize) + bc, _ := aes.NewCipher(k.cryptBytes()) + cipher.NewCBCEncrypter(bc, iv).CryptBlocks(p[:n], p[:n]) + genhmac(p[n:n], tok[:payOffset+n], k.signBytes()) + return payOffset + n + sha256.Size +} + +// token length for input msg of length n, not including base64 +func encodedLen(n int) int { + const k = aes.BlockSize + return n/k*k + k + overhead +} + +// max msg length for tok of length n, for binary token (no base64) +// upper bound; not exact +func decodedLen(n int) int { + return n - overhead +} + +// if msg is nil, decrypts in place and returns a slice of tok. +func verify(msg, tok []byte, ttl time.Duration, now time.Time, k *Key) []byte { + if len(tok) < 1 || tok[0] != version { + return nil + } + ts := time.Unix(int64(binary.BigEndian.Uint64(tok[1:])), 0) + if ttl >= 0 && (now.After(ts.Add(ttl)) || ts.After(now.Add(maxClockSkew))) { + return nil + } + n := len(tok) - sha256.Size + var hmac [sha256.Size]byte + genhmac(hmac[:0], tok[:n], k.signBytes()) + if subtle.ConstantTimeCompare(tok[n:], hmac[:]) != 1 { + return nil + } + pay := tok[payOffset : len(tok)-sha256.Size] + if len(pay)%aes.BlockSize != 0 { + return nil + } + if msg != nil { + copy(msg, pay) + pay = msg + } + bc, _ := aes.NewCipher(k.cryptBytes()) + iv := tok[9:][:aes.BlockSize] + cipher.NewCBCDecrypter(bc, iv).CryptBlocks(pay, pay) + return unpad(pay) +} + +// Pads p to a multiple of k using PKCS #7 standard block padding. +// See http://tools.ietf.org/html/rfc5652#section-6.3. +func pad(q, p []byte, k int) int { + n := len(p)/k*k + k + copy(q, p) + c := byte(n - len(p)) + for i := len(p); i < n; i++ { + q[i] = c + } + return n +} + +// Removes PKCS #7 standard block padding from p. +// See http://tools.ietf.org/html/rfc5652#section-6.3. +// This function is the inverse of pad. +// If the padding is not well-formed, unpad returns nil. +func unpad(p []byte) []byte { + c := p[len(p)-1] + for i := len(p) - int(c); i < len(p); i++ { + if i < 0 || p[i] != c { + return nil + } + } + return p[:len(p)-int(c)] +} + +func b64enc(src []byte) []byte { + dst := make([]byte, encoding.EncodedLen(len(src))) + encoding.Encode(dst, src) + return dst +} + +func b64dec(src []byte) []byte { + dst := make([]byte, encoding.DecodedLen(len(src))) + n, err := encoding.Decode(dst, src) + if err != nil { + return nil + } + return dst[:n] +} + +func genhmac(q, p, k []byte) { + h := hmac.New(sha256.New, k) + h.Write(p) + h.Sum(q) +} + +// EncryptAndSign encrypts and signs msg with key k and returns the resulting +// fernet token. If msg contains text, the text should be encoded +// with UTF-8 to follow fernet convention. +func EncryptAndSign(msg []byte, k *Key) (tok []byte, err error) { + iv := make([]byte, aes.BlockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + b := make([]byte, encodedLen(len(msg))) + n := gen(b, msg, iv, time.Now(), k) + tok = make([]byte, encoding.EncodedLen(n)) + encoding.Encode(tok, b[:n]) + return tok, nil +} + +// VerifyAndDecrypt verifies that tok is a valid fernet token that was signed +// with a key in k at most ttl time ago only if ttl is greater than zero. +// Returns the message contained in tok if tok is valid, otherwise nil. +func VerifyAndDecrypt(tok []byte, ttl time.Duration, k []*Key) (msg []byte) { + b := make([]byte, encoding.DecodedLen(len(tok))) + n, _ := encoding.Decode(b, tok) + for _, k1 := range k { + msg = verify(nil, b[:n], ttl, time.Now(), k1) + if msg != nil { + return msg + } + } + return nil +} diff --git a/vendor/github.com/fernet/fernet-go/key.go b/vendor/github.com/fernet/fernet-go/key.go new file mode 100644 index 0000000000..595217ed60 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/key.go @@ -0,0 +1,91 @@ +package fernet + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "errors" + "io" +) + +var ( + errKeyLen = errors.New("fernet: key decodes to wrong size") + errNoKeys = errors.New("fernet: no keys provided") +) + +// Key represents a key. +type Key [32]byte + +func (k *Key) cryptBytes() []byte { + return k[len(k)/2:] +} + +func (k *Key) signBytes() []byte { + return k[:len(k)/2] +} + +// Generate initializes k with pseudorandom data from package crypto/rand. +func (k *Key) Generate() error { + _, err := io.ReadFull(rand.Reader, k[:]) + return err +} + +// Encode returns the URL-safe base64 encoding of k. +func (k *Key) Encode() string { + return encoding.EncodeToString(k[:]) +} + +// DecodeKey decodes a key from s and returns it. The key can be in +// hexadecimal, standard base64, or URL-safe base64. +func DecodeKey(s string) (*Key, error) { + var b []byte + var err error + if s == "" { + return nil, errors.New("empty key") + } + if len(s) == hex.EncodedLen(len(Key{})) { + b, err = hex.DecodeString(s) + } else { + b, err = base64.StdEncoding.DecodeString(s) + if err != nil { + b, err = base64.URLEncoding.DecodeString(s) + } + } + if err != nil { + return nil, err + } + if len(b) != len(Key{}) { + return nil, errKeyLen + } + k := new(Key) + copy(k[:], b) + return k, nil +} + +// DecodeKeys decodes each element of a using DecodeKey and returns the +// resulting keys. Requires at least one key. +func DecodeKeys(a ...string) ([]*Key, error) { + if len(a) == 0 { + return nil, errNoKeys + } + var err error + ks := make([]*Key, len(a)) + for i, s := range a { + ks[i], err = DecodeKey(s) + if err != nil { + return nil, err + } + } + return ks, nil +} + +// MustDecodeKeys is like DecodeKeys, but panics if an error occurs. +// It simplifies safe initialization of global variables holding +// keys. +func MustDecodeKeys(a ...string) []*Key { + k, err := DecodeKeys(a...) + if err != nil { + panic(err) + } + return k +}