From 65f997163b0eac9699c35ed1b090f7cdf95ee8f0 Mon Sep 17 00:00:00 2001 From: Paul Gottschling Date: Mon, 23 Dec 2024 10:12:56 -0500 Subject: [PATCH 01/30] Fix TTL mistake in a partial (#50447) Closes #47068 The TTL shown in a `tctl auth sign` command does not correspond to the description of the TTL. Fix the description while making the TTL an integer divisible by 24. --- .../pages/includes/database-access/tctl-auth-sign-3-files.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/includes/database-access/tctl-auth-sign-3-files.mdx b/docs/pages/includes/database-access/tctl-auth-sign-3-files.mdx index 253c54b2121b4..a84fab0c7d9ad 100644 --- a/docs/pages/includes/database-access/tctl-auth-sign-3-files.mdx +++ b/docs/pages/includes/database-access/tctl-auth-sign-3-files.mdx @@ -30,12 +30,12 @@ the database, follow these instructions on your workstation: ``` 1. Export Teleport's certificate authority and a generate certificate/key pair. - This example generates a certificate with a 1-year validity period. + This example generates a certificate with a 90-day validity period. `db.example.com` is the hostname where the Teleport Database Service can reach the {{ dbname }} server. ```code - $ tctl auth sign --format={{ format }} --host=db.example.com --out=server --ttl=2190h + $ tctl auth sign --format={{ format }} --host=db.example.com --out=server --ttl=2160h ``` (!docs/pages/includes/database-access/ttl-note.mdx!) From 53a46bafc48203926d3e5430ac5da96fe066cb12 Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Mon, 23 Dec 2024 11:15:56 -0500 Subject: [PATCH 02/30] fixes an config indentation bug in the tbot chart (#50526) --- examples/chart/tbot/.lint/full.yaml | 2 +- examples/chart/tbot/templates/_config.tpl | 4 ++-- examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap | 4 ++++ .../chart/tbot/tests/__snapshot__/deployment_test.yaml.snap | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/chart/tbot/.lint/full.yaml b/examples/chart/tbot/.lint/full.yaml index 706b3d207ee5c..9d076782b506e 100644 --- a/examples/chart/tbot/.lint/full.yaml +++ b/examples/chart/tbot/.lint/full.yaml @@ -1,7 +1,7 @@ clusterName: "test.teleport.sh" teleportAuthAddress: "my-auth:3024" defaultOutput: - enabled: false + enabled: true token: "my-token" joinMethod: "modified-join-method" diff --git a/examples/chart/tbot/templates/_config.tpl b/examples/chart/tbot/templates/_config.tpl index 344b25e403fc5..d8c9aff491862 100644 --- a/examples/chart/tbot/templates/_config.tpl +++ b/examples/chart/tbot/templates/_config.tpl @@ -40,10 +40,10 @@ outputs: name: {{ include "tbot.defaultOutputName" . }} {{- end }} {{- if .Values.outputs }} -{{- toYaml .Values.outputs | nindent 6}} +{{- toYaml .Values.outputs | nindent 2}} {{- end }} {{- end }} {{- if .Values.services }} -services: {{- toYaml .Values.services | nindent 6}} +services: {{- toYaml .Values.services | nindent 2}} {{- end }} {{- end -}} diff --git a/examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap b/examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap index a1e58c58b4643..2dbfb81532f58 100644 --- a/examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap +++ b/examples/chart/tbot/tests/__snapshot__/config_test.yaml.snap @@ -35,6 +35,10 @@ should match the snapshot (full): join_method: modified-join-method token: my-token outputs: + - destination: + name: RELEASE-NAME-tbot-out + type: kubernetes_secret + type: identity - app_name: foo destination: path: /bar diff --git a/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap index d8353e0a90436..3b7179aff68ef 100644 --- a/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap @@ -22,7 +22,7 @@ should match the snapshot (full): template: metadata: annotations: - checksum/config: 094cdbfc4e4fe3824a33426d8eea4e9e8a4b2711823d4fbb4102e11caa7f62c0 + checksum/config: 010d3421120a26bed12d1b9df8443e0eeafa362e88bd830e4a81688d13689483 test-key: test-annotation-pod labels: app.kubernetes.io/component: tbot From 909aca2bd42eaea54992a5d1313e709ed2d9b817 Mon Sep 17 00:00:00 2001 From: matheus Date: Mon, 23 Dec 2024 14:26:40 -0300 Subject: [PATCH 03/30] Add Contacts Audit Events (#49755) (#50542) * Add Contact resource; include it in default editor permissions and web ACL * Add contacts to ACL test * Improve godocs * Add `contact` to the web user context * Add contact audit events * Lint fix * Use string interpolation * Comment ContactType enum * make grpc --------- Co-authored-by: Zac Bergquist Co-authored-by: Grzegorz Zdunek --- .../teleport/legacy/types/events/events.proto | 97 + api/types/events/events.go | 8 + api/types/events/events.pb.go | 4081 +++++++++++------ api/types/events/oneof.go | 8 + lib/events/api.go | 5 + lib/events/codes.go | 5 + lib/events/dynamic.go | 4 + lib/events/events_test.go | 2 + .../src/Audit/EventList/EventTypeCell.tsx | 2 + .../teleport/src/services/audit/makeEvent.ts | 25 + .../teleport/src/services/audit/types.ts | 16 + 11 files changed, 2755 insertions(+), 1498 deletions(-) diff --git a/api/proto/teleport/legacy/types/events/events.proto b/api/proto/teleport/legacy/types/events/events.proto index 5b51bc0a1a5c7..483b8272bc240 100644 --- a/api/proto/teleport/legacy/types/events/events.proto +++ b/api/proto/teleport/legacy/types/events/events.proto @@ -4694,6 +4694,8 @@ message OneOf { events.UserTaskUpdate UserTaskUpdate = 189; events.UserTaskDelete UserTaskDelete = 190; events.SFTPSummary SFTPSummary = 191; + events.ContactCreate ContactCreate = 192; + events.ContactDelete ContactDelete = 193; events.WorkloadIdentityCreate WorkloadIdentityCreate = 194; events.WorkloadIdentityUpdate WorkloadIdentityUpdate = 195; events.WorkloadIdentityDelete WorkloadIdentityDelete = 196; @@ -7738,3 +7740,98 @@ message UserLoginAccessListInvalid { (gogoproto.jsontag) = "" ]; } + +// ContactCreate is emitted when a contact is created. +message ContactCreate { + // Metadata is a common event metadata + Metadata Metadata = 1 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // ResourceMetadata is a common resource event metadata + ResourceMetadata Resource = 2 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // User is a common user event metadata + UserMetadata User = 3 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // ConnectionMetadata holds information about the connection + ConnectionMetadata Connection = 4 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // Status indicates whether the creation was successful. + Status Status = 5 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // Email is the Email of the contact being deleted + string Email = 6 [(gogoproto.jsontag) = "email"]; + + // ContactType is the type of the contact being deleted ('Business' or 'Security') + ContactType ContactType = 7 [(gogoproto.jsontag) = "contact_type"]; +} + +// ContactDelete is emitted when a contact is deleted. +message ContactDelete { + // Metadata is a common event metadata + Metadata Metadata = 1 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // ResourceMetadata is a common resource event metadata + ResourceMetadata Resource = 2 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // User is a common user event metadata + UserMetadata User = 3 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // ConnectionMetadata holds information about the connection + ConnectionMetadata Connection = 4 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // Status indicates whether the deletion was successful. + Status Status = 5 [ + (gogoproto.nullable) = false, + (gogoproto.embed) = true, + (gogoproto.jsontag) = "" + ]; + + // Email is the Email of the contact being deleted + string Email = 6 [(gogoproto.jsontag) = "email"]; + + // ContactType is the type of the contact being deleted ('Business' or 'Security') + ContactType ContactType = 7 [(gogoproto.jsontag) = "contact_type"]; +} + +// ContactType is the type of contact being added. +enum ContactType { + CONTACT_TYPE_UNSPECIFIED = 0; + CONTACT_TYPE_BUSINESS = 1; + CONTACT_TYPE_SECURITY = 2; +} diff --git a/api/types/events/events.go b/api/types/events/events.go index 2ed1598a8176b..13a33cb57918a 100644 --- a/api/types/events/events.go +++ b/api/types/events/events.go @@ -2413,3 +2413,11 @@ func (m *WorkloadIdentityUpdate) TrimToMaxSize(maxSize int) AuditEvent { func (m *WorkloadIdentityDelete) TrimToMaxSize(_ int) AuditEvent { return m } + +func (m *ContactCreate) TrimToMaxSize(_ int) AuditEvent { + return m +} + +func (m *ContactDelete) TrimToMaxSize(_ int) AuditEvent { + return m +} diff --git a/api/types/events/events.pb.go b/api/types/events/events.pb.go index 2ff3c85bd80e7..b182c9a47e1ba 100644 --- a/api/types/events/events.pb.go +++ b/api/types/events/events.pb.go @@ -350,6 +350,35 @@ func (AdminActionsMFAStatus) EnumDescriptor() ([]byte, []int) { return fileDescriptor_007ba1c3d6266d56, []int{7} } +// ContactType is the type of contact being added. +type ContactType int32 + +const ( + ContactType_CONTACT_TYPE_UNSPECIFIED ContactType = 0 + ContactType_CONTACT_TYPE_BUSINESS ContactType = 1 + ContactType_CONTACT_TYPE_SECURITY ContactType = 2 +) + +var ContactType_name = map[int32]string{ + 0: "CONTACT_TYPE_UNSPECIFIED", + 1: "CONTACT_TYPE_BUSINESS", + 2: "CONTACT_TYPE_SECURITY", +} + +var ContactType_value = map[string]int32{ + "CONTACT_TYPE_UNSPECIFIED": 0, + "CONTACT_TYPE_BUSINESS": 1, + "CONTACT_TYPE_SECURITY": 2, +} + +func (x ContactType) String() string { + return proto.EnumName(ContactType_name, int32(x)) +} + +func (ContactType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_007ba1c3d6266d56, []int{8} +} + // Operation is the network operation that was performed or attempted type SessionNetwork_NetworkOperation int32 @@ -7842,6 +7871,8 @@ type OneOf struct { // *OneOf_UserTaskUpdate // *OneOf_UserTaskDelete // *OneOf_SFTPSummary + // *OneOf_ContactCreate + // *OneOf_ContactDelete // *OneOf_WorkloadIdentityCreate // *OneOf_WorkloadIdentityUpdate // *OneOf_WorkloadIdentityDelete @@ -8452,6 +8483,12 @@ type OneOf_UserTaskDelete struct { type OneOf_SFTPSummary struct { SFTPSummary *SFTPSummary `protobuf:"bytes,191,opt,name=SFTPSummary,proto3,oneof" json:"SFTPSummary,omitempty"` } +type OneOf_ContactCreate struct { + ContactCreate *ContactCreate `protobuf:"bytes,192,opt,name=ContactCreate,proto3,oneof" json:"ContactCreate,omitempty"` +} +type OneOf_ContactDelete struct { + ContactDelete *ContactDelete `protobuf:"bytes,193,opt,name=ContactDelete,proto3,oneof" json:"ContactDelete,omitempty"` +} type OneOf_WorkloadIdentityCreate struct { WorkloadIdentityCreate *WorkloadIdentityCreate `protobuf:"bytes,194,opt,name=WorkloadIdentityCreate,proto3,oneof" json:"WorkloadIdentityCreate,omitempty"` } @@ -8652,6 +8689,8 @@ func (*OneOf_UserTaskCreate) isOneOf_Event() {} func (*OneOf_UserTaskUpdate) isOneOf_Event() {} func (*OneOf_UserTaskDelete) isOneOf_Event() {} func (*OneOf_SFTPSummary) isOneOf_Event() {} +func (*OneOf_ContactCreate) isOneOf_Event() {} +func (*OneOf_ContactDelete) isOneOf_Event() {} func (*OneOf_WorkloadIdentityCreate) isOneOf_Event() {} func (*OneOf_WorkloadIdentityUpdate) isOneOf_Event() {} func (*OneOf_WorkloadIdentityDelete) isOneOf_Event() {} @@ -9973,6 +10012,20 @@ func (m *OneOf) GetSFTPSummary() *SFTPSummary { return nil } +func (m *OneOf) GetContactCreate() *ContactCreate { + if x, ok := m.GetEvent().(*OneOf_ContactCreate); ok { + return x.ContactCreate + } + return nil +} + +func (m *OneOf) GetContactDelete() *ContactDelete { + if x, ok := m.GetEvent().(*OneOf_ContactDelete); ok { + return x.ContactDelete + } + return nil +} + func (m *OneOf) GetWorkloadIdentityCreate() *WorkloadIdentityCreate { if x, ok := m.GetEvent().(*OneOf_WorkloadIdentityCreate); ok { return x.WorkloadIdentityCreate @@ -10191,6 +10244,8 @@ func (*OneOf) XXX_OneofWrappers() []interface{} { (*OneOf_UserTaskUpdate)(nil), (*OneOf_UserTaskDelete)(nil), (*OneOf_SFTPSummary)(nil), + (*OneOf_ContactCreate)(nil), + (*OneOf_ContactDelete)(nil), (*OneOf_WorkloadIdentityCreate)(nil), (*OneOf_WorkloadIdentityUpdate)(nil), (*OneOf_WorkloadIdentityDelete)(nil), @@ -15186,6 +15241,114 @@ func (m *UserLoginAccessListInvalid) XXX_DiscardUnknown() { var xxx_messageInfo_UserLoginAccessListInvalid proto.InternalMessageInfo +// ContactCreate is emitted when a contact is created. +type ContactCreate struct { + // Metadata is a common event metadata + Metadata `protobuf:"bytes,1,opt,name=Metadata,proto3,embedded=Metadata" json:""` + // ResourceMetadata is a common resource event metadata + ResourceMetadata `protobuf:"bytes,2,opt,name=Resource,proto3,embedded=Resource" json:""` + // User is a common user event metadata + UserMetadata `protobuf:"bytes,3,opt,name=User,proto3,embedded=User" json:""` + // ConnectionMetadata holds information about the connection + ConnectionMetadata `protobuf:"bytes,4,opt,name=Connection,proto3,embedded=Connection" json:""` + // Status indicates whether the creation was successful. + Status `protobuf:"bytes,5,opt,name=Status,proto3,embedded=Status" json:""` + // Email is the Email of the contact being deleted + Email string `protobuf:"bytes,6,opt,name=Email,proto3" json:"email"` + // ContactType is the type of the contact being deleted ('Business' or 'Security') + ContactType ContactType `protobuf:"varint,7,opt,name=ContactType,proto3,enum=events.ContactType" json:"contact_type"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ContactCreate) Reset() { *m = ContactCreate{} } +func (m *ContactCreate) String() string { return proto.CompactTextString(m) } +func (*ContactCreate) ProtoMessage() {} +func (*ContactCreate) Descriptor() ([]byte, []int) { + return fileDescriptor_007ba1c3d6266d56, []int{238} +} +func (m *ContactCreate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ContactCreate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ContactCreate.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ContactCreate) XXX_Merge(src proto.Message) { + xxx_messageInfo_ContactCreate.Merge(m, src) +} +func (m *ContactCreate) XXX_Size() int { + return m.Size() +} +func (m *ContactCreate) XXX_DiscardUnknown() { + xxx_messageInfo_ContactCreate.DiscardUnknown(m) +} + +var xxx_messageInfo_ContactCreate proto.InternalMessageInfo + +// ContactDelete is emitted when a contact is deleted. +type ContactDelete struct { + // Metadata is a common event metadata + Metadata `protobuf:"bytes,1,opt,name=Metadata,proto3,embedded=Metadata" json:""` + // ResourceMetadata is a common resource event metadata + ResourceMetadata `protobuf:"bytes,2,opt,name=Resource,proto3,embedded=Resource" json:""` + // User is a common user event metadata + UserMetadata `protobuf:"bytes,3,opt,name=User,proto3,embedded=User" json:""` + // ConnectionMetadata holds information about the connection + ConnectionMetadata `protobuf:"bytes,4,opt,name=Connection,proto3,embedded=Connection" json:""` + // Status indicates whether the deletion was successful. + Status `protobuf:"bytes,5,opt,name=Status,proto3,embedded=Status" json:""` + // Email is the Email of the contact being deleted + Email string `protobuf:"bytes,6,opt,name=Email,proto3" json:"email"` + // ContactType is the type of the contact being deleted ('Business' or 'Security') + ContactType ContactType `protobuf:"varint,7,opt,name=ContactType,proto3,enum=events.ContactType" json:"contact_type"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ContactDelete) Reset() { *m = ContactDelete{} } +func (m *ContactDelete) String() string { return proto.CompactTextString(m) } +func (*ContactDelete) ProtoMessage() {} +func (*ContactDelete) Descriptor() ([]byte, []int) { + return fileDescriptor_007ba1c3d6266d56, []int{239} +} +func (m *ContactDelete) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ContactDelete) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ContactDelete.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ContactDelete) XXX_Merge(src proto.Message) { + xxx_messageInfo_ContactDelete.Merge(m, src) +} +func (m *ContactDelete) XXX_Size() int { + return m.Size() +} +func (m *ContactDelete) XXX_DiscardUnknown() { + xxx_messageInfo_ContactDelete.DiscardUnknown(m) +} + +var xxx_messageInfo_ContactDelete proto.InternalMessageInfo + func init() { proto.RegisterEnum("events.UserKind", UserKind_name, UserKind_value) proto.RegisterEnum("events.EventAction", EventAction_name, EventAction_value) @@ -15195,6 +15358,7 @@ func init() { proto.RegisterEnum("events.ElasticsearchCategory", ElasticsearchCategory_name, ElasticsearchCategory_value) proto.RegisterEnum("events.OpenSearchCategory", OpenSearchCategory_name, OpenSearchCategory_value) proto.RegisterEnum("events.AdminActionsMFAStatus", AdminActionsMFAStatus_name, AdminActionsMFAStatus_value) + proto.RegisterEnum("events.ContactType", ContactType_name, ContactType_value) proto.RegisterEnum("events.SessionNetwork_NetworkOperation", SessionNetwork_NetworkOperation_name, SessionNetwork_NetworkOperation_value) proto.RegisterType((*Metadata)(nil), "events.Metadata") proto.RegisterType((*SessionMetadata)(nil), "events.SessionMetadata") @@ -15448,6 +15612,8 @@ func init() { proto.RegisterType((*WorkloadIdentityDelete)(nil), "events.WorkloadIdentityDelete") proto.RegisterType((*AccessListInvalidMetadata)(nil), "events.AccessListInvalidMetadata") proto.RegisterType((*UserLoginAccessListInvalid)(nil), "events.UserLoginAccessListInvalid") + proto.RegisterType((*ContactCreate)(nil), "events.ContactCreate") + proto.RegisterType((*ContactDelete)(nil), "events.ContactDelete") } func init() { @@ -15455,1085 +15621,1094 @@ func init() { } var fileDescriptor_007ba1c3d6266d56 = []byte{ - // 17244 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x6b, 0x78, 0x24, 0x49, - 0x72, 0x18, 0x86, 0x7e, 0xa0, 0x01, 0x04, 0x9e, 0x93, 0xf3, 0xaa, 0x9d, 0x9d, 0x59, 0xec, 0xd6, - 0xee, 0xcd, 0xcd, 0xec, 0xed, 0x62, 0x6e, 0x67, 0x67, 0x77, 0x6f, 0x5f, 0xdc, 0x6b, 0xa0, 0x81, - 0x41, 0xcf, 0xe0, 0xd1, 0x5b, 0x8d, 0x99, 0xb9, 0x3d, 0xf2, 0xae, 0x59, 0xe8, 0xca, 0x01, 0x6a, - 0xa7, 0xbb, 0xaa, 0x59, 0x55, 0x3d, 0x18, 0xac, 0xfc, 0xe0, 0xc9, 0x14, 0x45, 0x8a, 0xc7, 0xd3, - 0xf9, 0x68, 0xf2, 0x28, 0x4a, 0xb6, 0x8f, 0x92, 0x65, 0x53, 0x14, 0xc5, 0x33, 0x65, 0x9a, 0xe4, - 0x91, 0x3c, 0xeb, 0x71, 0xb2, 0x7c, 0x22, 0x3f, 0xf1, 0x23, 0x65, 0x5b, 0xd6, 0x67, 0xcb, 0x38, - 0x99, 0xb6, 0xfe, 0xe0, 0xb3, 0xfd, 0xd1, 0x36, 0x3f, 0xeb, 0x4c, 0x4b, 0xfe, 0xfc, 0x65, 0x64, - 0x56, 0x55, 0xd6, 0xab, 0xf1, 0x5c, 0x61, 0x71, 0x83, 0x3f, 0x33, 0xe8, 0x88, 0xc8, 0xc8, 0xac, - 0xc8, 0xc8, 0xcc, 0xc8, 0xcc, 0xc8, 0x08, 0xb8, 0xea, 0xd1, 0x16, 0xed, 0xd8, 0x8e, 0x77, 0xad, - 0x45, 0xd7, 0xf4, 0xe6, 0xe6, 0x35, 0x6f, 0xb3, 0x43, 0xdd, 0x6b, 0xf4, 0x21, 0xb5, 0x3c, 0xff, - 0xbf, 0xa9, 0x8e, 0x63, 0x7b, 0x36, 0x29, 0xf1, 0x5f, 0x17, 0xce, 0xac, 0xd9, 0x6b, 0x36, 0x82, - 0xae, 0xb1, 0xbf, 0x38, 0xf6, 0xc2, 0xc5, 0x35, 0xdb, 0x5e, 0x6b, 0xd1, 0x6b, 0xf8, 0x6b, 0xb5, - 0x7b, 0xff, 0x9a, 0xeb, 0x39, 0xdd, 0xa6, 0x27, 0xb0, 0x93, 0x71, 0xac, 0x67, 0xb6, 0xa9, 0xeb, - 0xe9, 0xed, 0x8e, 0x20, 0x78, 0x2a, 0x4e, 0xb0, 0xe1, 0xe8, 0x9d, 0x0e, 0x75, 0x44, 0xe5, 0x17, - 0x3e, 0x1e, 0xb4, 0x53, 0x6f, 0x36, 0xa9, 0xeb, 0xb6, 0x4c, 0xd7, 0xbb, 0xf6, 0xf0, 0x25, 0xe9, - 0x97, 0x20, 0x7c, 0x26, 0xfd, 0x83, 0xf0, 0x5f, 0x41, 0xf2, 0x62, 0x3a, 0x89, 0x5f, 0x63, 0xac, - 0x6a, 0xf5, 0x2b, 0x79, 0x18, 0x5c, 0xa4, 0x9e, 0x6e, 0xe8, 0x9e, 0x4e, 0x2e, 0x42, 0x7f, 0xd5, - 0x32, 0xe8, 0x23, 0x25, 0xf7, 0x74, 0xee, 0x4a, 0x61, 0xba, 0xb4, 0xbd, 0x35, 0x99, 0xa7, 0xa6, - 0xc6, 0x81, 0xe4, 0x12, 0x14, 0x57, 0x36, 0x3b, 0x54, 0xc9, 0x3f, 0x9d, 0xbb, 0x32, 0x34, 0x3d, - 0xb4, 0xbd, 0x35, 0xd9, 0x8f, 0x42, 0xd3, 0x10, 0x4c, 0x9e, 0x81, 0x7c, 0xb5, 0xa2, 0x14, 0x10, - 0x79, 0x6a, 0x7b, 0x6b, 0x72, 0xb4, 0x6b, 0x1a, 0x2f, 0xd8, 0x6d, 0xd3, 0xa3, 0xed, 0x8e, 0xb7, - 0xa9, 0xe5, 0xab, 0x15, 0x72, 0x19, 0x8a, 0x33, 0xb6, 0x41, 0x95, 0x22, 0x12, 0x91, 0xed, 0xad, - 0xc9, 0xb1, 0xa6, 0x6d, 0x50, 0x89, 0x0a, 0xf1, 0xe4, 0xd3, 0x50, 0x5c, 0x31, 0xdb, 0x54, 0xe9, - 0x7f, 0x3a, 0x77, 0x65, 0xf8, 0xfa, 0x85, 0x29, 0x2e, 0xbe, 0x29, 0x5f, 0x7c, 0x53, 0x2b, 0xbe, - 0x7c, 0xa7, 0x27, 0xbe, 0xbd, 0x35, 0xd9, 0xb7, 0xbd, 0x35, 0x59, 0x64, 0x22, 0xff, 0xf2, 0x77, - 0x26, 0x73, 0x1a, 0x96, 0x24, 0x6f, 0xc1, 0xf0, 0x4c, 0xab, 0xeb, 0x7a, 0xd4, 0x59, 0xd2, 0xdb, - 0x54, 0x29, 0x61, 0x85, 0x17, 0xb6, 0xb7, 0x26, 0xcf, 0x35, 0x39, 0xb8, 0x61, 0xe9, 0x6d, 0xb9, - 0x62, 0x99, 0x5c, 0xfd, 0x8d, 0x1c, 0x8c, 0xd7, 0xa9, 0xeb, 0x9a, 0xb6, 0x15, 0xc8, 0xe6, 0x63, - 0x30, 0x24, 0x40, 0xd5, 0x0a, 0xca, 0x67, 0x68, 0x7a, 0x60, 0x7b, 0x6b, 0xb2, 0xe0, 0x9a, 0x86, - 0x16, 0x62, 0xc8, 0x27, 0x61, 0xe0, 0x9e, 0xe9, 0xad, 0x2f, 0xce, 0x95, 0x85, 0x9c, 0xce, 0x6d, - 0x6f, 0x4d, 0x92, 0x0d, 0xd3, 0x5b, 0x6f, 0xb4, 0xef, 0xeb, 0x52, 0x85, 0x3e, 0x19, 0x59, 0x80, - 0x89, 0x9a, 0x63, 0x3e, 0xd4, 0x3d, 0x7a, 0x9b, 0x6e, 0xd6, 0xec, 0x96, 0xd9, 0xdc, 0x14, 0x52, - 0x7c, 0x7a, 0x7b, 0x6b, 0xf2, 0x62, 0x87, 0xe3, 0x1a, 0x0f, 0xe8, 0x66, 0xa3, 0x83, 0x58, 0x89, - 0x49, 0xa2, 0xa4, 0xfa, 0x9b, 0x25, 0x18, 0xb9, 0xe3, 0x52, 0x27, 0x68, 0xf7, 0x65, 0x28, 0xb2, - 0xdf, 0xa2, 0xc9, 0x28, 0xf3, 0xae, 0x4b, 0x1d, 0x59, 0xe6, 0x0c, 0x4f, 0xae, 0x42, 0xff, 0x82, - 0xbd, 0x66, 0x5a, 0xa2, 0xd9, 0xa7, 0xb7, 0xb7, 0x26, 0xc7, 0x5b, 0x0c, 0x20, 0x51, 0x72, 0x0a, - 0xf2, 0x7d, 0x30, 0x52, 0x6d, 0x33, 0x1d, 0xb2, 0x2d, 0xdd, 0xb3, 0x1d, 0xd1, 0x5a, 0x94, 0xae, - 0x29, 0xc1, 0xa5, 0x82, 0x11, 0x7a, 0xf2, 0x06, 0x40, 0xf9, 0x5e, 0x5d, 0xb3, 0x5b, 0xb4, 0xac, - 0x2d, 0x09, 0x65, 0xc0, 0xd2, 0xfa, 0x86, 0xdb, 0x70, 0xec, 0x16, 0x6d, 0xe8, 0x8e, 0x5c, 0xad, - 0x44, 0x4d, 0x66, 0x61, 0xac, 0x8c, 0xa3, 0x42, 0xa3, 0x3f, 0xd4, 0xa5, 0xae, 0xe7, 0x2a, 0xfd, - 0x4f, 0x17, 0xae, 0x0c, 0x4d, 0x5f, 0xda, 0xde, 0x9a, 0x7c, 0x82, 0x8f, 0x97, 0x86, 0x23, 0x50, - 0x12, 0x8b, 0x58, 0x21, 0x32, 0x0d, 0xa3, 0xe5, 0x0f, 0xba, 0x0e, 0xad, 0x1a, 0xd4, 0xf2, 0x4c, - 0x6f, 0x53, 0x68, 0xc8, 0xc5, 0xed, 0xad, 0x49, 0x45, 0x67, 0x88, 0x86, 0x29, 0x30, 0x12, 0x93, - 0x68, 0x11, 0xb2, 0x0c, 0xa7, 0x6e, 0xce, 0xd4, 0xea, 0xd4, 0x79, 0x68, 0x36, 0x69, 0xb9, 0xd9, - 0xb4, 0xbb, 0x96, 0xa7, 0x0c, 0x20, 0x9f, 0x67, 0xb6, 0xb7, 0x26, 0x2f, 0xad, 0x35, 0x3b, 0x0d, - 0x97, 0x63, 0x1b, 0x3a, 0x47, 0x4b, 0xcc, 0x92, 0x65, 0xc9, 0x67, 0x61, 0x74, 0xc5, 0x61, 0x5a, - 0x68, 0x54, 0x28, 0x83, 0x2b, 0x83, 0xa8, 0xff, 0xe7, 0xa6, 0xc4, 0x4c, 0xc5, 0xa1, 0x7e, 0xcf, - 0xf2, 0xc6, 0x7a, 0xbc, 0x40, 0xc3, 0x40, 0x9c, 0xdc, 0xd8, 0x08, 0x2b, 0x42, 0x41, 0x61, 0x1f, - 0x6f, 0x3a, 0xd4, 0x48, 0x68, 0xdb, 0x10, 0xb6, 0xf9, 0xea, 0xf6, 0xd6, 0xe4, 0xc7, 0x1c, 0x41, - 0xd3, 0xe8, 0xa9, 0x76, 0x99, 0xac, 0xc8, 0x2c, 0x0c, 0x32, 0x6d, 0xba, 0x6d, 0x5a, 0x86, 0x02, - 0x4f, 0xe7, 0xae, 0x8c, 0x5d, 0x9f, 0xf0, 0x5b, 0xef, 0xc3, 0xa7, 0xcf, 0x6f, 0x6f, 0x4d, 0x9e, - 0x66, 0x3a, 0xd8, 0x78, 0x60, 0x5a, 0xf2, 0x14, 0x11, 0x14, 0x65, 0xa3, 0x68, 0xda, 0xf6, 0x70, - 0xe8, 0x0e, 0x87, 0xa3, 0x68, 0xd5, 0xf6, 0xe2, 0xc3, 0xd6, 0x27, 0x23, 0x33, 0x30, 0x3a, 0x6d, - 0x7b, 0x55, 0xcb, 0xf5, 0x74, 0xab, 0x49, 0xab, 0x15, 0x65, 0x04, 0xcb, 0xa1, 0x5a, 0xb0, 0x72, - 0xa6, 0xc0, 0x34, 0x22, 0x93, 0x52, 0xb4, 0x8c, 0xfa, 0x2f, 0x8a, 0x30, 0xc6, 0xfa, 0x44, 0x1a, - 0x3e, 0x65, 0x36, 0x13, 0x30, 0x08, 0xab, 0xc5, 0xed, 0xe8, 0x4d, 0x2a, 0x46, 0x12, 0x7e, 0x85, - 0xe5, 0x03, 0x25, 0x9e, 0x71, 0x7a, 0x72, 0x15, 0x06, 0x39, 0xa8, 0x5a, 0x11, 0x83, 0x6b, 0x74, - 0x7b, 0x6b, 0x72, 0xc8, 0x45, 0x58, 0xc3, 0x34, 0xb4, 0x00, 0xcd, 0xb4, 0x9b, 0xff, 0x3d, 0x6f, - 0xbb, 0x1e, 0x63, 0x2e, 0xc6, 0x16, 0x7e, 0x86, 0x28, 0xb0, 0x2e, 0x50, 0xb2, 0x76, 0x47, 0x0b, - 0x91, 0xd7, 0x01, 0x38, 0xa4, 0x6c, 0x18, 0x8e, 0x18, 0x60, 0x4f, 0x6c, 0x6f, 0x4d, 0x9e, 0x15, - 0x2c, 0x74, 0xc3, 0x90, 0x47, 0xa7, 0x44, 0x4c, 0xda, 0x30, 0xc2, 0x7f, 0x2d, 0xe8, 0xab, 0xb4, - 0xc5, 0x47, 0xd7, 0xf0, 0xf5, 0x2b, 0x7e, 0x27, 0x46, 0xa5, 0x33, 0x25, 0x93, 0xce, 0x5a, 0x9e, - 0xb3, 0x39, 0x3d, 0x29, 0x26, 0xe4, 0xf3, 0xa2, 0xaa, 0x16, 0xe2, 0xe4, 0xa9, 0x40, 0x2e, 0xc3, - 0xe6, 0xe9, 0x39, 0xdb, 0xd9, 0xd0, 0x1d, 0x83, 0x1a, 0xd3, 0x9b, 0xf2, 0x3c, 0x7d, 0xdf, 0x07, - 0x37, 0x56, 0x65, 0xd5, 0x93, 0xc9, 0x59, 0xa7, 0x73, 0x6e, 0xf5, 0xee, 0x2a, 0xaa, 0xdc, 0x40, - 0x42, 0x5a, 0x6e, 0x77, 0x35, 0xae, 0x66, 0xd1, 0x32, 0x6c, 0x2a, 0xe0, 0x80, 0xbb, 0xd4, 0x61, - 0x93, 0x38, 0x8e, 0x3a, 0x31, 0x15, 0x08, 0x26, 0x0f, 0x39, 0x26, 0xc9, 0x43, 0x14, 0xb9, 0xf0, - 0x0e, 0x9c, 0x4a, 0x88, 0x82, 0x4c, 0x40, 0xe1, 0x01, 0xdd, 0xe4, 0xea, 0xa2, 0xb1, 0x3f, 0xc9, - 0x19, 0xe8, 0x7f, 0xa8, 0xb7, 0xba, 0x62, 0x09, 0xd5, 0xf8, 0x8f, 0x37, 0xf2, 0x9f, 0xca, 0xb1, - 0x15, 0x87, 0xcc, 0xd8, 0x96, 0x45, 0x9b, 0x9e, 0xbc, 0xe8, 0xbc, 0x0a, 0x43, 0x0b, 0x76, 0x53, - 0x6f, 0x61, 0x3f, 0x72, 0xbd, 0x53, 0xb6, 0xb7, 0x26, 0xcf, 0xb0, 0x0e, 0x9c, 0x6a, 0x31, 0x8c, - 0xd4, 0xa6, 0x90, 0x94, 0x29, 0x80, 0x46, 0xdb, 0xb6, 0x47, 0xb1, 0x60, 0x3e, 0x54, 0x00, 0x2c, - 0xe8, 0x20, 0x4a, 0x56, 0x80, 0x90, 0x98, 0x5c, 0x83, 0xc1, 0x1a, 0x5b, 0x67, 0x9b, 0x76, 0x4b, - 0x28, 0x1f, 0x2e, 0x05, 0xb8, 0xf6, 0xca, 0x63, 0xd5, 0x27, 0x52, 0xe7, 0x61, 0x6c, 0xa6, 0x65, - 0x52, 0xcb, 0x93, 0x5b, 0xcd, 0x46, 0x72, 0x79, 0x8d, 0x5a, 0x9e, 0xdc, 0x6a, 0x1c, 0xf3, 0x3a, - 0x83, 0xca, 0xad, 0x0e, 0x48, 0xd5, 0xdf, 0x2b, 0xc0, 0x13, 0xb7, 0xbb, 0xab, 0xd4, 0xb1, 0xa8, - 0x47, 0x5d, 0xb1, 0x20, 0x07, 0x5c, 0x97, 0xe0, 0x54, 0x02, 0x29, 0xb8, 0xe3, 0x42, 0xf9, 0x20, - 0x40, 0x36, 0xc4, 0x1a, 0x2f, 0xcf, 0xb6, 0x89, 0xa2, 0x64, 0x1e, 0xc6, 0x43, 0x20, 0x6b, 0x84, - 0xab, 0xe4, 0x71, 0x29, 0x79, 0x6a, 0x7b, 0x6b, 0xf2, 0x82, 0xc4, 0x8d, 0x35, 0x5b, 0xd6, 0xe0, - 0x78, 0x31, 0x72, 0x1b, 0x26, 0x42, 0xd0, 0x4d, 0xc7, 0xee, 0x76, 0x5c, 0xa5, 0x80, 0xac, 0x26, - 0xb7, 0xb7, 0x26, 0x9f, 0x94, 0x58, 0xad, 0x21, 0x52, 0x5e, 0xc0, 0xe3, 0x05, 0xc9, 0x8f, 0xe4, - 0x64, 0x6e, 0x62, 0x14, 0x16, 0x71, 0x14, 0xbe, 0xe6, 0x8f, 0xc2, 0x4c, 0x21, 0x4d, 0xc5, 0x4b, - 0x8a, 0x41, 0x19, 0x6b, 0x46, 0x62, 0x50, 0x26, 0x6a, 0xbc, 0x30, 0x03, 0x67, 0x53, 0x79, 0xed, - 0x49, 0xab, 0xff, 0x79, 0x41, 0xe6, 0x52, 0xb3, 0x8d, 0xa0, 0x33, 0x97, 0xe5, 0xce, 0xac, 0xd9, - 0x06, 0x4e, 0xf5, 0xb9, 0x70, 0xed, 0x94, 0x1a, 0xdb, 0xb1, 0x8d, 0xf8, 0xac, 0x9f, 0x2c, 0x4b, - 0x3e, 0x0f, 0xe7, 0x12, 0x40, 0x3e, 0x5d, 0x73, 0xed, 0xbf, 0xbc, 0xbd, 0x35, 0xa9, 0xa6, 0x70, - 0x8d, 0xcf, 0xde, 0x19, 0x5c, 0x88, 0x0e, 0xe7, 0x25, 0xa9, 0xdb, 0x96, 0xa7, 0x9b, 0x96, 0x30, - 0x2e, 0xf9, 0x28, 0xf9, 0xf8, 0xf6, 0xd6, 0xe4, 0xb3, 0xb2, 0x0e, 0xfa, 0x34, 0xf1, 0xc6, 0x67, - 0xf1, 0x21, 0x06, 0x28, 0x29, 0xa8, 0x6a, 0x5b, 0x5f, 0xf3, 0x2d, 0xe6, 0x2b, 0xdb, 0x5b, 0x93, - 0xcf, 0xa5, 0xd6, 0x61, 0x32, 0x2a, 0x79, 0x85, 0xce, 0xe2, 0x44, 0x34, 0x20, 0x21, 0x6e, 0xc9, - 0x36, 0x28, 0x7e, 0x43, 0x3f, 0xf2, 0x57, 0xb7, 0xb7, 0x26, 0x9f, 0x92, 0xf8, 0x5b, 0xb6, 0x41, - 0xe3, 0xcd, 0x4f, 0x29, 0xad, 0xfe, 0x46, 0x01, 0x9e, 0xaa, 0x97, 0x17, 0x17, 0xaa, 0x86, 0x6f, - 0xd2, 0xd4, 0x1c, 0xfb, 0xa1, 0x69, 0x48, 0xa3, 0x77, 0x15, 0xce, 0xc7, 0x50, 0xb3, 0x68, 0x45, - 0x05, 0xc6, 0x34, 0x7e, 0x9b, 0x6f, 0x2e, 0x75, 0x04, 0x4d, 0x83, 0x9b, 0x5a, 0xd1, 0x45, 0x3b, - 0x8b, 0x11, 0xeb, 0xa3, 0x18, 0xaa, 0xbe, 0x6e, 0x3b, 0x5e, 0xb3, 0xeb, 0x09, 0x25, 0xc0, 0x3e, - 0x4a, 0xd4, 0xe1, 0x0a, 0xa2, 0x1e, 0x55, 0xf8, 0x7c, 0xc8, 0x8f, 0xe7, 0x60, 0xa2, 0xec, 0x79, - 0x8e, 0xb9, 0xda, 0xf5, 0xe8, 0xa2, 0xde, 0xe9, 0x98, 0xd6, 0x1a, 0x8e, 0xf5, 0xe1, 0xeb, 0x6f, - 0x05, 0x6b, 0x64, 0x4f, 0x49, 0x4c, 0xc5, 0x8b, 0x4b, 0x43, 0x54, 0xf7, 0x51, 0x8d, 0x36, 0xc7, - 0xc9, 0x43, 0x34, 0x5e, 0x8e, 0x0d, 0xd1, 0x54, 0x5e, 0x7b, 0x1a, 0xa2, 0x5f, 0x29, 0xc0, 0xc5, - 0xe5, 0x07, 0x9e, 0xae, 0x51, 0xd7, 0xee, 0x3a, 0x4d, 0xea, 0xde, 0xe9, 0x18, 0xba, 0x47, 0xc3, - 0x91, 0x3a, 0x09, 0xfd, 0x65, 0xc3, 0xa0, 0x06, 0xb2, 0xeb, 0xe7, 0xdb, 0x3e, 0x9d, 0x01, 0x34, - 0x0e, 0x27, 0x1f, 0x83, 0x01, 0x51, 0x06, 0xb9, 0xf7, 0x4f, 0x0f, 0x6f, 0x6f, 0x4d, 0x0e, 0x74, - 0x39, 0x48, 0xf3, 0x71, 0x8c, 0xac, 0x42, 0x5b, 0x94, 0x91, 0x15, 0x42, 0x32, 0x83, 0x83, 0x34, - 0x1f, 0x47, 0xde, 0x85, 0x31, 0x64, 0x1b, 0xb4, 0x47, 0xcc, 0x7d, 0x67, 0x7c, 0xe9, 0xca, 0x8d, - 0xe5, 0x4b, 0x13, 0xb6, 0xa6, 0xe1, 0xf8, 0x05, 0xb4, 0x18, 0x03, 0x72, 0x0f, 0x26, 0x44, 0x23, - 0x42, 0xa6, 0xfd, 0x3d, 0x98, 0x9e, 0xdd, 0xde, 0x9a, 0x3c, 0x25, 0xda, 0x2f, 0xb1, 0x4d, 0x30, - 0x61, 0x8c, 0x45, 0xb3, 0x43, 0xc6, 0xa5, 0x9d, 0x18, 0x8b, 0x2f, 0x96, 0x19, 0xc7, 0x99, 0xa8, - 0xef, 0xc1, 0x88, 0x5c, 0x90, 0x9c, 0xc3, 0xad, 0x35, 0x1f, 0x27, 0xb8, 0x29, 0x37, 0x0d, 0xdc, - 0x4f, 0xbf, 0x04, 0xc3, 0x15, 0xea, 0x36, 0x1d, 0xb3, 0xc3, 0xac, 0x06, 0xa1, 0xe4, 0xe3, 0xdb, - 0x5b, 0x93, 0xc3, 0x46, 0x08, 0xd6, 0x64, 0x1a, 0xf5, 0xff, 0xce, 0xc1, 0x39, 0xc6, 0xbb, 0xec, - 0xba, 0xe6, 0x9a, 0xd5, 0x96, 0x97, 0xed, 0x17, 0xa0, 0x54, 0xc7, 0xfa, 0x44, 0x4d, 0x67, 0xb6, - 0xb7, 0x26, 0x27, 0x78, 0x0b, 0x24, 0x3d, 0x14, 0x34, 0xc1, 0xbe, 0x32, 0xbf, 0xc3, 0xbe, 0x92, - 0x99, 0xb4, 0x9e, 0xee, 0x78, 0xa6, 0xb5, 0x56, 0xf7, 0x74, 0xaf, 0xeb, 0x46, 0x4c, 0x5a, 0x81, - 0x69, 0xb8, 0x88, 0x8a, 0x98, 0xb4, 0x91, 0x42, 0xe4, 0x1d, 0x18, 0x99, 0xb5, 0x8c, 0x90, 0x09, - 0x9f, 0x10, 0x9f, 0x64, 0x96, 0x26, 0x45, 0x78, 0x92, 0x45, 0xa4, 0x80, 0xfa, 0x37, 0x72, 0xa0, - 0xf0, 0x4d, 0xe0, 0x82, 0xe9, 0x7a, 0x8b, 0xb4, 0xbd, 0x2a, 0xcd, 0x4e, 0x73, 0xfe, 0xae, 0x92, - 0xe1, 0xa4, 0xb5, 0x08, 0x4d, 0x01, 0xb1, 0xab, 0x6c, 0x99, 0x6e, 0x62, 0xfb, 0x11, 0x2b, 0x45, - 0xaa, 0x30, 0xc0, 0x39, 0x73, 0x5b, 0x62, 0xf8, 0xba, 0xe2, 0x2b, 0x42, 0xbc, 0x6a, 0xae, 0x0c, - 0x6d, 0x4e, 0x2c, 0x6f, 0x68, 0x44, 0x79, 0xf5, 0x6b, 0x05, 0x98, 0x88, 0x17, 0x22, 0xf7, 0x60, - 0xf0, 0x96, 0x6d, 0x5a, 0xd4, 0x58, 0xb6, 0xb0, 0x85, 0xbd, 0x0f, 0x47, 0x7c, 0x5b, 0xfc, 0xf4, - 0xfb, 0x58, 0xa6, 0x21, 0x5b, 0xb0, 0x78, 0x56, 0x12, 0x30, 0x23, 0x9f, 0x85, 0x21, 0x66, 0x03, - 0x3e, 0x44, 0xce, 0xf9, 0x1d, 0x39, 0x3f, 0x2d, 0x38, 0x9f, 0x71, 0x78, 0xa1, 0x24, 0xeb, 0x90, - 0x1d, 0xd3, 0x2b, 0x8d, 0xea, 0xae, 0x6d, 0x89, 0x9e, 0x47, 0xbd, 0x72, 0x10, 0x22, 0xeb, 0x15, - 0xa7, 0x61, 0xa6, 0x2b, 0xff, 0x58, 0xec, 0x06, 0x69, 0xef, 0xc2, 0x65, 0x15, 0xef, 0x01, 0x89, - 0x98, 0x58, 0x30, 0x2e, 0x04, 0xba, 0x6e, 0x76, 0xd0, 0xea, 0xc7, 0x75, 0x6d, 0xec, 0xfa, 0xe5, - 0x29, 0xff, 0x50, 0x6c, 0x4a, 0x3a, 0x52, 0x7b, 0xf8, 0xd2, 0xd4, 0x62, 0x40, 0x8e, 0x3b, 0x53, - 0xd4, 0xc9, 0x18, 0x0b, 0xb9, 0xb7, 0xdb, 0x11, 0x72, 0xf5, 0x47, 0xf3, 0xf0, 0x62, 0xd8, 0x45, - 0x1a, 0x7d, 0x68, 0xd2, 0x8d, 0x90, 0xa3, 0xd8, 0x23, 0xb3, 0x21, 0xe6, 0xce, 0xac, 0xeb, 0xd6, - 0x1a, 0x35, 0xc8, 0x55, 0xe8, 0xd7, 0xec, 0x16, 0x75, 0x95, 0x1c, 0x9a, 0x87, 0x38, 0x7d, 0x39, - 0x0c, 0x20, 0x1f, 0xb2, 0x20, 0x05, 0xb1, 0xa1, 0xb4, 0xe2, 0xe8, 0xa6, 0xe7, 0x6b, 0x52, 0x39, - 0xa9, 0x49, 0xbb, 0xa8, 0x71, 0x8a, 0xf3, 0xe0, 0x6b, 0x0c, 0x0a, 0xde, 0x43, 0x80, 0x2c, 0x78, - 0x4e, 0x72, 0xe1, 0x75, 0x18, 0x96, 0x88, 0xf7, 0xb4, 0x88, 0x7c, 0xa3, 0x28, 0x8f, 0x2d, 0xbf, - 0x59, 0x62, 0x6c, 0x5d, 0x63, 0x63, 0xc2, 0x75, 0x99, 0x15, 0xc3, 0x07, 0x95, 0xd0, 0x7c, 0x04, - 0x45, 0x35, 0x1f, 0x41, 0xe4, 0x65, 0x18, 0xe4, 0x2c, 0x82, 0xfd, 0x32, 0xee, 0xb5, 0x1d, 0x84, - 0x45, 0x4d, 0x81, 0x80, 0x90, 0xfc, 0x62, 0x0e, 0x2e, 0xf5, 0x94, 0x04, 0x2a, 0xdf, 0xf0, 0xf5, - 0x57, 0xf6, 0x25, 0xc6, 0xe9, 0x17, 0xb7, 0xb7, 0x26, 0xaf, 0x4a, 0x9a, 0xe1, 0x48, 0x34, 0x8d, - 0x26, 0x27, 0x92, 0xda, 0xd5, 0xbb, 0x29, 0xcc, 0x58, 0xe5, 0x95, 0xce, 0xe1, 0x51, 0x95, 0xd5, - 0xdc, 0xf4, 0x1b, 0x59, 0x0c, 0x8d, 0x55, 0xf1, 0xbd, 0xf7, 0x7d, 0x92, 0x94, 0x6a, 0x32, 0xb8, - 0x90, 0x26, 0x9c, 0xe7, 0x98, 0x8a, 0xbe, 0xb9, 0x7c, 0x7f, 0xd1, 0xb6, 0xbc, 0x75, 0xbf, 0x82, - 0x7e, 0xf9, 0xac, 0x07, 0x2b, 0x30, 0xf4, 0xcd, 0x86, 0x7d, 0xbf, 0xd1, 0x66, 0x54, 0x29, 0x75, - 0x64, 0x71, 0x62, 0x13, 0xbb, 0x18, 0xe3, 0xfe, 0x94, 0x57, 0x0a, 0x4f, 0xe2, 0xfc, 0x79, 0x21, - 0x39, 0xc1, 0xc5, 0x0a, 0xa9, 0x55, 0x18, 0x59, 0xb0, 0x9b, 0x0f, 0x02, 0x75, 0x79, 0x1d, 0x4a, - 0x2b, 0xba, 0xb3, 0x46, 0x3d, 0x94, 0xc5, 0xf0, 0xf5, 0x53, 0x53, 0xfc, 0x74, 0x9b, 0x11, 0x71, - 0xc4, 0xf4, 0x98, 0x98, 0x7d, 0x4a, 0x1e, 0xfe, 0xd6, 0x44, 0x01, 0xf5, 0x3b, 0xfd, 0x30, 0x22, - 0x4e, 0x62, 0x71, 0xf5, 0x20, 0x6f, 0x84, 0x67, 0xdb, 0x62, 0xba, 0x0c, 0x4e, 0xa3, 0x82, 0x53, - 0xb4, 0x11, 0xc6, 0xec, 0xf7, 0xb7, 0x26, 0x73, 0xdb, 0x5b, 0x93, 0x7d, 0xda, 0xa0, 0xb4, 0x89, - 0x0d, 0xd7, 0x37, 0x69, 0x41, 0x97, 0xcf, 0x56, 0x63, 0x65, 0xf9, 0x7a, 0xf7, 0x0e, 0x0c, 0x88, - 0x36, 0x08, 0x8d, 0x3b, 0x1f, 0x9e, 0x9d, 0x44, 0x4e, 0x94, 0x63, 0xa5, 0xfd, 0x52, 0xe4, 0x2d, - 0x28, 0xf1, 0xb3, 0x04, 0x21, 0x80, 0x73, 0xe9, 0x67, 0x2f, 0xb1, 0xe2, 0xa2, 0x0c, 0x99, 0x07, - 0x08, 0xcf, 0x11, 0x82, 0x03, 0x74, 0xc1, 0x21, 0x79, 0xc2, 0x10, 0xe3, 0x22, 0x95, 0x25, 0xaf, - 0xc2, 0xc8, 0x0a, 0x75, 0xda, 0xa6, 0xa5, 0xb7, 0xea, 0xe6, 0x07, 0xfe, 0x19, 0x3a, 0x2e, 0xf4, - 0xae, 0xf9, 0x81, 0x3c, 0x72, 0x23, 0x74, 0xe4, 0x73, 0x69, 0xfb, 0xf4, 0x01, 0x6c, 0xc8, 0x33, - 0x3b, 0x6e, 0x60, 0x63, 0xed, 0x49, 0xd9, 0xb6, 0xbf, 0x0b, 0xa3, 0x91, 0x2d, 0x9a, 0x38, 0x24, - 0xbd, 0x94, 0x64, 0x2d, 0xed, 0x37, 0x63, 0x6c, 0xa3, 0x1c, 0x98, 0x26, 0x57, 0x2d, 0xd3, 0x33, - 0xf5, 0xd6, 0x8c, 0xdd, 0x6e, 0xeb, 0x96, 0xa1, 0x0c, 0x85, 0x9a, 0x6c, 0x72, 0x4c, 0xa3, 0xc9, - 0x51, 0xb2, 0x26, 0x47, 0x0b, 0x91, 0xdb, 0x30, 0x21, 0xfa, 0x50, 0xa3, 0x4d, 0xdb, 0x61, 0xb6, - 0x07, 0x9e, 0x81, 0x8a, 0x63, 0x00, 0x97, 0xe3, 0x1a, 0x8e, 0x8f, 0x94, 0x8d, 0xfb, 0x78, 0xc1, - 0x5b, 0xc5, 0xc1, 0xe1, 0x89, 0x91, 0xf8, 0xb1, 0xb5, 0xfa, 0xd7, 0x0a, 0x30, 0x2c, 0x48, 0xd9, - 0xd2, 0x7d, 0xa2, 0xe0, 0x07, 0x51, 0xf0, 0x54, 0x45, 0x2d, 0x1d, 0x96, 0xa2, 0xaa, 0x5f, 0xcc, - 0x07, 0xb3, 0x51, 0xcd, 0x31, 0xad, 0x83, 0xcd, 0x46, 0x97, 0x01, 0x66, 0xd6, 0xbb, 0xd6, 0x03, - 0x7e, 0x3d, 0x97, 0x0f, 0xaf, 0xe7, 0x9a, 0xa6, 0x26, 0x61, 0xc8, 0x25, 0x28, 0x56, 0x18, 0x7f, - 0xd6, 0x33, 0x23, 0xd3, 0x43, 0xdf, 0xe6, 0x9c, 0x72, 0x2f, 0x6a, 0x08, 0x66, 0x9b, 0xb9, 0xe9, - 0x4d, 0x8f, 0x72, 0xf3, 0xb9, 0xc0, 0x37, 0x73, 0xab, 0x0c, 0xa0, 0x71, 0x38, 0xb9, 0x01, 0xa7, - 0x2a, 0xb4, 0xa5, 0x6f, 0x2e, 0x9a, 0xad, 0x96, 0xe9, 0xd2, 0xa6, 0x6d, 0x19, 0x2e, 0x0a, 0x59, - 0x54, 0xd7, 0x76, 0xb5, 0x24, 0x01, 0x51, 0xa1, 0xb4, 0x7c, 0xff, 0xbe, 0x4b, 0x3d, 0x14, 0x5f, - 0x61, 0x1a, 0xd8, 0xe4, 0x6c, 0x23, 0x44, 0x13, 0x18, 0xf5, 0xeb, 0x39, 0xb6, 0x5b, 0x72, 0x1f, - 0x78, 0x76, 0x27, 0xd0, 0xf2, 0x03, 0x89, 0xe4, 0x6a, 0x68, 0x57, 0xe4, 0xf1, 0x6b, 0xc7, 0xc5, - 0xd7, 0x0e, 0x08, 0xdb, 0x22, 0xb4, 0x28, 0x52, 0xbf, 0xaa, 0xb0, 0xc3, 0x57, 0xa9, 0x7f, 0x94, - 0x87, 0xf3, 0xa2, 0xc5, 0x33, 0x2d, 0xb3, 0xb3, 0x6a, 0xeb, 0x8e, 0xa1, 0xd1, 0x26, 0x35, 0x1f, - 0xd2, 0xe3, 0x39, 0xf0, 0xa2, 0x43, 0xa7, 0x78, 0x80, 0xa1, 0x73, 0x1d, 0x37, 0x9e, 0x4c, 0x32, - 0x78, 0xc0, 0xcc, 0x8d, 0x8a, 0x89, 0xed, 0xad, 0xc9, 0x11, 0x83, 0x83, 0xf1, 0x8a, 0x41, 0x93, - 0x89, 0x98, 0x92, 0x2c, 0x50, 0x6b, 0xcd, 0x5b, 0x47, 0x25, 0xe9, 0xe7, 0x4a, 0xd2, 0x42, 0x88, - 0x26, 0x30, 0xea, 0xff, 0x96, 0x87, 0x33, 0x71, 0x91, 0xd7, 0xa9, 0x65, 0x9c, 0xc8, 0xfb, 0xc3, - 0x91, 0xf7, 0x1f, 0x17, 0xe0, 0x49, 0x51, 0xa6, 0xbe, 0xae, 0x3b, 0xd4, 0xa8, 0x98, 0x0e, 0x6d, - 0x7a, 0xb6, 0xb3, 0x79, 0x8c, 0x0d, 0xa8, 0xc3, 0x13, 0xfb, 0x0d, 0x28, 0x89, 0xe3, 0x06, 0xbe, - 0xce, 0x8c, 0x05, 0x2d, 0x41, 0x68, 0x62, 0x85, 0xe2, 0x47, 0x15, 0xb1, 0xce, 0x2a, 0xed, 0xa6, - 0xb3, 0x3e, 0x05, 0xa3, 0x81, 0xe8, 0x71, 0xe3, 0x3b, 0x10, 0x5a, 0x5b, 0x86, 0x8f, 0xc0, 0xbd, - 0xaf, 0x16, 0x25, 0xc4, 0xda, 0x7c, 0x40, 0xb5, 0x82, 0xd6, 0xd0, 0xa8, 0xa8, 0x2d, 0x28, 0x67, - 0x1a, 0x9a, 0x4c, 0xa4, 0x6e, 0x15, 0xe1, 0x42, 0x7a, 0xb7, 0x6b, 0x54, 0x37, 0x4e, 0x7a, 0xfd, - 0x7b, 0xb2, 0xd7, 0xc9, 0x33, 0x50, 0xac, 0xe9, 0xde, 0xba, 0xb8, 0xee, 0xc7, 0x3b, 0xe8, 0xfb, - 0x66, 0x8b, 0x36, 0x3a, 0xba, 0xb7, 0xae, 0x21, 0x4a, 0x9a, 0x33, 0x00, 0x39, 0xa6, 0xcc, 0x19, - 0xd2, 0x62, 0x3f, 0xfc, 0x74, 0xee, 0x4a, 0x31, 0x75, 0xb1, 0xff, 0x4e, 0x31, 0x6b, 0x5e, 0xb9, - 0xe7, 0x98, 0x1e, 0x3d, 0xd1, 0xb0, 0x13, 0x0d, 0x3b, 0xa0, 0x86, 0xfd, 0xa3, 0x3c, 0x8c, 0x06, - 0x9b, 0xa6, 0xf7, 0x69, 0xf3, 0x68, 0xd6, 0xaa, 0x70, 0x2b, 0x53, 0x38, 0xf0, 0x56, 0xe6, 0x20, - 0x0a, 0xa5, 0x06, 0x47, 0xac, 0xdc, 0x34, 0x40, 0x89, 0xf1, 0x23, 0xd6, 0xe0, 0x60, 0xf5, 0x19, - 0x18, 0x58, 0xd4, 0x1f, 0x99, 0xed, 0x6e, 0x5b, 0x58, 0xe9, 0xe8, 0xbe, 0xd6, 0xd6, 0x1f, 0x69, - 0x3e, 0x5c, 0xfd, 0x6f, 0x72, 0x30, 0x26, 0x84, 0x2a, 0x98, 0x1f, 0x48, 0xaa, 0xa1, 0x74, 0xf2, - 0x07, 0x96, 0x4e, 0x61, 0xff, 0xd2, 0x51, 0xff, 0x52, 0x01, 0x94, 0x39, 0xb3, 0x45, 0x57, 0x1c, - 0xdd, 0x72, 0xef, 0x53, 0x47, 0x6c, 0xa7, 0x67, 0x19, 0xab, 0x03, 0x7d, 0xa0, 0x34, 0xa5, 0xe4, - 0xf7, 0x35, 0xa5, 0x7c, 0x02, 0x86, 0x44, 0x63, 0x02, 0xd7, 0x49, 0x1c, 0x35, 0x8e, 0x0f, 0xd4, - 0x42, 0x3c, 0x23, 0x2e, 0x77, 0x3a, 0x8e, 0xfd, 0x90, 0x3a, 0xfc, 0x56, 0x4c, 0x10, 0xeb, 0x3e, - 0x50, 0x0b, 0xf1, 0x12, 0x67, 0xea, 0xdb, 0x8b, 0x32, 0x67, 0xea, 0x68, 0x21, 0x9e, 0x5c, 0x81, - 0xc1, 0x05, 0xbb, 0xa9, 0xa3, 0xa0, 0xf9, 0xb4, 0x32, 0xb2, 0xbd, 0x35, 0x39, 0xd8, 0x12, 0x30, - 0x2d, 0xc0, 0x32, 0xca, 0x8a, 0xbd, 0x61, 0xb5, 0x6c, 0x9d, 0x3b, 0xdb, 0x0c, 0x72, 0x4a, 0x43, - 0xc0, 0xb4, 0x00, 0xcb, 0x28, 0x99, 0xcc, 0xd1, 0x89, 0x69, 0x30, 0xe4, 0x79, 0x5f, 0xc0, 0xb4, - 0x00, 0xab, 0x7e, 0xbd, 0xc8, 0xb4, 0xd7, 0x35, 0x3f, 0x78, 0xec, 0xd7, 0x85, 0x70, 0xc0, 0xf4, - 0xef, 0x63, 0xc0, 0x3c, 0x36, 0x07, 0x76, 0xea, 0xbf, 0x18, 0x00, 0x10, 0xd2, 0x9f, 0x3d, 0xd9, - 0x1c, 0x1e, 0x4c, 0x6b, 0x2a, 0x70, 0x6a, 0xd6, 0x5a, 0xd7, 0xad, 0x26, 0x35, 0xc2, 0x63, 0xcb, - 0x12, 0x0e, 0x6d, 0x74, 0xba, 0xa4, 0x02, 0x19, 0x9e, 0x5b, 0x6a, 0xc9, 0x02, 0xe4, 0x25, 0x18, - 0xae, 0x5a, 0x1e, 0x75, 0xf4, 0xa6, 0x67, 0x3e, 0xa4, 0x62, 0x6a, 0xc0, 0x9b, 0x68, 0x33, 0x04, - 0x6b, 0x32, 0x0d, 0xb9, 0x01, 0x23, 0x35, 0xdd, 0xf1, 0xcc, 0xa6, 0xd9, 0xd1, 0x2d, 0xcf, 0x55, - 0x06, 0x71, 0x46, 0x43, 0x0b, 0xa3, 0x23, 0xc1, 0xb5, 0x08, 0x15, 0xf9, 0x1c, 0x0c, 0xe1, 0xd6, - 0x14, 0xfd, 0xc3, 0x87, 0x76, 0xbc, 0xa8, 0x7c, 0x36, 0x74, 0x47, 0xe4, 0xa7, 0xaf, 0x78, 0xe3, - 0x1c, 0xbf, 0xab, 0x0c, 0x38, 0x92, 0xcf, 0xc0, 0xc0, 0xac, 0x65, 0x20, 0x73, 0xd8, 0x91, 0xb9, - 0x2a, 0x98, 0x9f, 0x0b, 0x99, 0xdb, 0x9d, 0x18, 0x6f, 0x9f, 0x5d, 0xfa, 0x28, 0x1b, 0xfe, 0xf0, - 0x46, 0xd9, 0xc8, 0x87, 0x70, 0x2c, 0x3e, 0x7a, 0x58, 0xc7, 0xe2, 0x63, 0xfb, 0x3c, 0x16, 0x57, - 0x3f, 0x80, 0xe1, 0xe9, 0xda, 0x5c, 0x30, 0x7a, 0x9f, 0x80, 0x42, 0x4d, 0x78, 0x46, 0x14, 0xb9, - 0x3d, 0xd3, 0x31, 0x0d, 0x8d, 0xc1, 0xc8, 0x55, 0x18, 0x9c, 0x41, 0x77, 0x3b, 0x71, 0x8b, 0x58, - 0xe4, 0xeb, 0x5f, 0x13, 0x61, 0xe8, 0x75, 0xeb, 0xa3, 0xc9, 0xc7, 0x60, 0xa0, 0xe6, 0xd8, 0x6b, - 0x8e, 0xde, 0x16, 0x6b, 0x30, 0xba, 0xa6, 0x74, 0x38, 0x48, 0xf3, 0x71, 0xea, 0x4f, 0xe5, 0x7c, - 0xb3, 0x9d, 0x95, 0xa8, 0x77, 0xf1, 0x68, 0x1e, 0xeb, 0x1e, 0xe4, 0x25, 0x5c, 0x0e, 0xd2, 0x7c, - 0x1c, 0xb9, 0x0a, 0xfd, 0xb3, 0x8e, 0x63, 0x3b, 0xb2, 0x4f, 0x3d, 0x65, 0x00, 0xf9, 0xba, 0x17, - 0x29, 0xc8, 0x6b, 0x30, 0xcc, 0xe7, 0x1c, 0x7e, 0xa2, 0x59, 0xe8, 0x75, 0x53, 0x2a, 0x53, 0xaa, - 0xdf, 0x2a, 0x48, 0x36, 0x1b, 0x97, 0xf8, 0x63, 0x78, 0x2b, 0xf0, 0x32, 0x14, 0xa6, 0x6b, 0x73, - 0x62, 0x02, 0x3c, 0xed, 0x17, 0x95, 0x54, 0x25, 0x56, 0x8e, 0x51, 0x93, 0x8b, 0x50, 0xac, 0x31, - 0xf5, 0x29, 0xa1, 0x7a, 0x0c, 0x6e, 0x6f, 0x4d, 0x16, 0x3b, 0x4c, 0x7f, 0x10, 0x8a, 0x58, 0xb6, - 0x99, 0xe1, 0x3b, 0x26, 0x8e, 0x0d, 0xf7, 0x31, 0x17, 0xa1, 0x58, 0x76, 0xd6, 0x1e, 0x8a, 0x59, - 0x0b, 0xb1, 0xba, 0xb3, 0xf6, 0x50, 0x43, 0x28, 0xb9, 0x06, 0xa0, 0x51, 0xaf, 0xeb, 0x58, 0xf8, - 0xdc, 0x65, 0x08, 0xcf, 0xdf, 0x70, 0x36, 0x74, 0x10, 0xda, 0x68, 0xda, 0x06, 0xd5, 0x24, 0x12, - 0xf5, 0xaf, 0x86, 0x17, 0x3b, 0x15, 0xd3, 0x7d, 0x70, 0xd2, 0x85, 0x7b, 0xe8, 0x42, 0x5d, 0x1c, - 0x71, 0x26, 0x3b, 0x69, 0x12, 0xfa, 0xe7, 0x5a, 0xfa, 0x9a, 0x8b, 0x7d, 0x28, 0x7c, 0xd7, 0xee, - 0x33, 0x80, 0xc6, 0xe1, 0xb1, 0x7e, 0x1a, 0xdc, 0xb9, 0x9f, 0xbe, 0xda, 0x1f, 0x8c, 0xb6, 0x25, - 0xea, 0x6d, 0xd8, 0xce, 0x49, 0x57, 0xed, 0xb6, 0xab, 0x2e, 0xc3, 0x40, 0xdd, 0x69, 0x4a, 0x47, - 0x17, 0xb8, 0x1f, 0x70, 0x9d, 0x26, 0x3f, 0xb6, 0xf0, 0x91, 0x8c, 0xae, 0xe2, 0x7a, 0x48, 0x37, - 0x10, 0xd2, 0x19, 0xae, 0x27, 0xe8, 0x04, 0x52, 0xd0, 0xd5, 0x6c, 0xc7, 0x13, 0x1d, 0x17, 0xd0, - 0x75, 0x6c, 0xc7, 0xd3, 0x7c, 0x24, 0xf9, 0x04, 0xc0, 0xca, 0x4c, 0xcd, 0x77, 0xee, 0x1f, 0x0a, - 0x7d, 0x0f, 0x85, 0x57, 0xbf, 0x26, 0xa1, 0xc9, 0x0a, 0x0c, 0x2d, 0x77, 0xa8, 0xc3, 0xb7, 0x42, - 0xfc, 0x01, 0xcb, 0xc7, 0x63, 0xa2, 0x15, 0xfd, 0x3e, 0x25, 0xfe, 0x0f, 0xc8, 0xf9, 0xfa, 0x62, - 0xfb, 0x3f, 0xb5, 0x90, 0x11, 0x79, 0x0d, 0x4a, 0x65, 0x6e, 0xe7, 0x0d, 0x23, 0xcb, 0x40, 0x64, - 0xb8, 0x05, 0xe5, 0x28, 0xbe, 0x67, 0xd7, 0xf1, 0x6f, 0x4d, 0x90, 0xab, 0x57, 0x61, 0x22, 0x5e, - 0x0d, 0x19, 0x86, 0x81, 0x99, 0xe5, 0xa5, 0xa5, 0xd9, 0x99, 0x95, 0x89, 0x3e, 0x32, 0x08, 0xc5, - 0xfa, 0xec, 0x52, 0x65, 0x22, 0xa7, 0xfe, 0x92, 0x34, 0x83, 0x30, 0xd5, 0x3a, 0xb9, 0x1a, 0x3e, - 0xd0, 0x7d, 0xcb, 0x04, 0xde, 0x87, 0xe2, 0x89, 0x41, 0xdb, 0xf4, 0x3c, 0x6a, 0x88, 0x55, 0x02, - 0xef, 0x0b, 0xbd, 0x47, 0x5a, 0x02, 0x4f, 0x5e, 0x80, 0x51, 0x84, 0x89, 0x2b, 0x42, 0xbe, 0x3f, - 0x16, 0x05, 0x9c, 0x47, 0x5a, 0x14, 0xa9, 0xfe, 0x6e, 0x78, 0x3b, 0xbc, 0x40, 0xf5, 0xe3, 0x7a, - 0xa3, 0xf8, 0x11, 0xe9, 0x2f, 0xf5, 0x5f, 0x16, 0xf9, 0x93, 0x13, 0xfe, 0x3e, 0xf1, 0x28, 0x44, - 0x19, 0x1e, 0xe9, 0x16, 0xf6, 0x70, 0xa4, 0xfb, 0x02, 0x94, 0x16, 0xa9, 0xb7, 0x6e, 0xfb, 0x8e, - 0x5f, 0xe8, 0xa1, 0xd7, 0x46, 0x88, 0xec, 0xa1, 0xc7, 0x69, 0xc8, 0x03, 0x20, 0xfe, 0xe3, 0xc3, - 0xc0, 0xf1, 0xdb, 0x3f, 0x42, 0x3e, 0x9f, 0xd8, 0xa7, 0xd4, 0xf1, 0x89, 0x32, 0xfa, 0xf4, 0x9f, - 0x09, 0x1c, 0xcb, 0x25, 0x4f, 0xac, 0x3f, 0xd9, 0x9a, 0x2c, 0x71, 0x1a, 0x2d, 0x85, 0x2d, 0x79, - 0x17, 0x86, 0x16, 0xe7, 0xca, 0xe2, 0x21, 0x22, 0xf7, 0x8a, 0x78, 0x22, 0x90, 0xa2, 0x8f, 0x08, - 0x44, 0x82, 0xef, 0x7b, 0xda, 0xf7, 0xf5, 0xe4, 0x3b, 0xc4, 0x90, 0x0b, 0xd3, 0x16, 0xfe, 0x52, - 0x48, 0x9c, 0x2e, 0x04, 0xda, 0x12, 0x7d, 0x3f, 0x14, 0x97, 0x15, 0xc7, 0xc6, 0xb4, 0x65, 0xf0, - 0x00, 0xa3, 0x7b, 0x19, 0x4e, 0x95, 0x3b, 0x9d, 0x96, 0x49, 0x0d, 0xd4, 0x17, 0xad, 0xdb, 0xa2, - 0xae, 0x70, 0xf9, 0xc1, 0xc7, 0x27, 0x3a, 0x47, 0x36, 0xf0, 0xf9, 0x6b, 0xc3, 0xe9, 0x46, 0xfd, - 0x33, 0x93, 0x65, 0xd5, 0x9f, 0xc9, 0xc3, 0xb9, 0x19, 0x87, 0xea, 0x1e, 0x5d, 0x9c, 0x2b, 0x97, - 0xbb, 0xe8, 0x23, 0xd7, 0x6a, 0x51, 0x6b, 0xed, 0x68, 0x86, 0xf5, 0x9b, 0x30, 0x16, 0x34, 0xa0, - 0xde, 0xb4, 0x3b, 0x54, 0x7e, 0xc8, 0xd5, 0xf4, 0x31, 0x0d, 0x97, 0xa1, 0xb4, 0x18, 0x29, 0xb9, - 0x0d, 0xa7, 0x03, 0x48, 0xb9, 0xd5, 0xb2, 0x37, 0x34, 0xda, 0x75, 0xb9, 0x23, 0xee, 0x20, 0x77, - 0xc4, 0x0d, 0x39, 0xe8, 0x0c, 0xdf, 0x70, 0x18, 0x81, 0x96, 0x56, 0x4a, 0xfd, 0x5a, 0x01, 0xce, - 0xdf, 0xd5, 0x5b, 0xa6, 0x11, 0x8a, 0x46, 0xa3, 0x6e, 0xc7, 0xb6, 0x5c, 0x7a, 0x8c, 0x46, 0x69, - 0x64, 0x28, 0x14, 0x0f, 0x65, 0x28, 0x24, 0xbb, 0xa8, 0xff, 0xc0, 0x5d, 0x54, 0xda, 0x57, 0x17, - 0xfd, 0xaf, 0x39, 0x98, 0xf0, 0x1f, 0x1a, 0xc8, 0x8f, 0xc6, 0x25, 0x2f, 0x78, 0x3c, 0x42, 0x8c, - 0xf9, 0x5d, 0x23, 0x9e, 0xd4, 0x61, 0x60, 0xf6, 0x51, 0xc7, 0x74, 0xa8, 0xbb, 0x0b, 0xa7, 0xf1, - 0x4b, 0xe2, 0xb8, 0xe4, 0x14, 0xe5, 0x45, 0x12, 0x27, 0x25, 0x1c, 0x8c, 0xcf, 0x07, 0xf9, 0x53, - 0x8b, 0x69, 0xff, 0x25, 0x3c, 0x7f, 0x3e, 0x28, 0x9e, 0x64, 0x44, 0xde, 0x83, 0x86, 0xa4, 0xe4, - 0x59, 0x28, 0xac, 0xac, 0x2c, 0x88, 0x99, 0x14, 0x23, 0x10, 0x78, 0x9e, 0xfc, 0x3e, 0x92, 0x61, - 0xd5, 0x7f, 0x9a, 0x07, 0x60, 0xaa, 0xc0, 0x87, 0xeb, 0x91, 0x28, 0xe1, 0x34, 0x0c, 0xfa, 0x02, - 0x17, 0x6a, 0x18, 0xbc, 0x12, 0x88, 0x77, 0x44, 0xbc, 0xee, 0xe0, 0x45, 0xc8, 0xa4, 0xef, 0x48, - 0xce, 0xef, 0x01, 0x70, 0x67, 0x83, 0x8e, 0xe4, 0xbe, 0xfb, 0xf8, 0x27, 0x60, 0x48, 0xcc, 0x78, - 0x76, 0xe4, 0xfc, 0xbf, 0xe9, 0x03, 0xb5, 0x10, 0x1f, 0x9b, 0x5a, 0x4b, 0x07, 0x58, 0x88, 0x7d, - 0xf1, 0xf2, 0x5e, 0x39, 0x11, 0xef, 0x21, 0x8b, 0xf7, 0x4b, 0x42, 0xbc, 0xfc, 0xc5, 0xd0, 0xb1, - 0x15, 0xef, 0xa1, 0x9d, 0x7d, 0xab, 0xff, 0x28, 0x07, 0x84, 0x35, 0xab, 0xa6, 0xbb, 0xee, 0x86, - 0xed, 0x18, 0xdc, 0x39, 0xfd, 0x48, 0x04, 0x73, 0x78, 0xf7, 0x95, 0xdf, 0x1a, 0x84, 0xd3, 0x11, - 0xc7, 0xdf, 0x63, 0x3e, 0x59, 0x5d, 0x8d, 0x8e, 0xa6, 0x5e, 0xaf, 0x5e, 0x9e, 0x93, 0x2f, 0x44, - 0xfb, 0x23, 0x0f, 0xde, 0xa4, 0x9b, 0xd0, 0x17, 0x61, 0x44, 0xfc, 0x60, 0x2b, 0xb4, 0x7f, 0xd3, - 0x85, 0xa3, 0xd4, 0x65, 0x00, 0x2d, 0x82, 0x26, 0xaf, 0xc0, 0x10, 0x1b, 0x30, 0x6b, 0x18, 0xac, - 0x64, 0x20, 0x7c, 0x51, 0x62, 0xf8, 0x40, 0x79, 0x3d, 0x09, 0x28, 0xa5, 0x77, 0x4b, 0x83, 0xbb, - 0x78, 0xb7, 0xf4, 0x79, 0x18, 0x2e, 0x5b, 0x96, 0xed, 0xe1, 0x26, 0xdd, 0x15, 0x57, 0x13, 0x99, - 0x56, 0xf9, 0xb3, 0xf8, 0x18, 0x3f, 0xa4, 0x4f, 0x35, 0xcb, 0x65, 0x86, 0xe4, 0xba, 0xff, 0x2a, - 0x86, 0x3a, 0xc2, 0xab, 0x1c, 0xaf, 0x67, 0x1c, 0x01, 0x4b, 0x3e, 0x8a, 0xc1, 0xce, 0x1b, 0xad, - 0x39, 0x76, 0xc7, 0x76, 0xa9, 0xc1, 0x05, 0x35, 0x1c, 0x86, 0x36, 0xe8, 0x08, 0x04, 0xbe, 0x9b, - 0x8b, 0x04, 0x0e, 0x89, 0x14, 0x21, 0xf7, 0xe1, 0x8c, 0x7f, 0x51, 0x1c, 0xbc, 0x50, 0xac, 0x56, - 0x5c, 0x65, 0x04, 0x5f, 0x25, 0x91, 0xb8, 0x32, 0x54, 0x2b, 0xd3, 0x4f, 0xf9, 0xd7, 0x22, 0xfe, - 0x13, 0xc7, 0x86, 0x69, 0xc8, 0x5d, 0x9d, 0xca, 0x8f, 0xfc, 0x20, 0x0c, 0x2f, 0xea, 0x8f, 0x2a, - 0x5d, 0x71, 0xf6, 0x32, 0xba, 0xfb, 0xdb, 0x97, 0xb6, 0xfe, 0xa8, 0x61, 0x88, 0x72, 0x31, 0x9b, - 0x42, 0x66, 0x49, 0x1a, 0x70, 0xae, 0xe6, 0xd8, 0x6d, 0xdb, 0xa3, 0x46, 0xec, 0xb1, 0xdf, 0x78, - 0xf8, 0x3a, 0xb8, 0x23, 0x28, 0x1a, 0x3d, 0x5e, 0xfd, 0x65, 0xb0, 0x21, 0x6d, 0x18, 0x2f, 0xbb, - 0x6e, 0xb7, 0x4d, 0xc3, 0x1b, 0xaa, 0x89, 0x1d, 0x3f, 0xe3, 0xe3, 0xc2, 0x6b, 0xf9, 0x49, 0x1d, - 0x8b, 0xf2, 0x0b, 0xaa, 0x86, 0x67, 0xca, 0x35, 0xe2, 0xb7, 0xc4, 0x79, 0xdf, 0x2a, 0x0e, 0x8e, - 0x4d, 0x8c, 0x6b, 0xe7, 0x93, 0x8d, 0x59, 0x31, 0xbd, 0x16, 0x55, 0xbf, 0x99, 0x03, 0x08, 0x05, - 0x4c, 0x5e, 0x8c, 0x46, 0x44, 0xca, 0x85, 0x17, 0x1d, 0x22, 0x5a, 0x42, 0x24, 0x04, 0x12, 0xb9, - 0x08, 0x45, 0x8c, 0xa8, 0x91, 0x0f, 0x0f, 0x56, 0x1f, 0x98, 0x96, 0xa1, 0x21, 0x94, 0x61, 0xa5, - 0xa7, 0xef, 0x88, 0xc5, 0x4b, 0x7d, 0x6e, 0x15, 0x56, 0x60, 0xbc, 0xde, 0x5d, 0xf5, 0xeb, 0x96, - 0xde, 0xf1, 0x61, 0x60, 0x0f, 0xb7, 0xbb, 0x1a, 0x3c, 0x7e, 0x8d, 0x84, 0x4d, 0x89, 0x16, 0x51, - 0xbf, 0x9e, 0x8b, 0xcd, 0x82, 0x47, 0xb8, 0xe8, 0x3d, 0x97, 0xf4, 0xd3, 0x48, 0x4e, 0x4b, 0xea, - 0x1f, 0x14, 0x60, 0xb8, 0x66, 0x3b, 0x9e, 0x08, 0x51, 0x72, 0xbc, 0x57, 0x21, 0x69, 0xaf, 0x54, - 0xdc, 0xc3, 0x5e, 0xe9, 0x22, 0x14, 0x25, 0x17, 0x65, 0x7e, 0x2f, 0x62, 0x18, 0x8e, 0x86, 0xd0, - 0x0f, 0xf9, 0xc9, 0x45, 0xf2, 0x12, 0x74, 0xe0, 0xc0, 0xae, 0x06, 0x3f, 0x9c, 0x07, 0xf8, 0xcc, - 0x4b, 0x2f, 0x3d, 0xc6, 0x5d, 0xaa, 0xfe, 0xc5, 0x1c, 0x8c, 0x8b, 0xab, 0x45, 0x29, 0x1a, 0xda, - 0x80, 0x7f, 0x29, 0x2c, 0xcf, 0x24, 0x1c, 0xa4, 0xf9, 0x38, 0xb6, 0x68, 0xcd, 0x3e, 0x32, 0x3d, - 0xbc, 0x5d, 0x91, 0xc2, 0xa1, 0x51, 0x01, 0x93, 0x17, 0x2d, 0x9f, 0x8e, 0xbc, 0xe8, 0x5f, 0x9a, - 0x16, 0xc2, 0x95, 0x9a, 0x15, 0x98, 0x4d, 0xbd, 0x38, 0x55, 0x7f, 0xad, 0x08, 0xc5, 0xd9, 0x47, - 0xb4, 0x79, 0xcc, 0xbb, 0x46, 0x3a, 0x8a, 0x2d, 0x1e, 0xf0, 0x28, 0x76, 0x3f, 0x5e, 0x20, 0xef, - 0x84, 0xfd, 0x59, 0x8a, 0x56, 0x1f, 0xeb, 0xf9, 0x78, 0xf5, 0x7e, 0x4f, 0x1f, 0x3f, 0x27, 0xa2, - 0xff, 0xb2, 0x00, 0x85, 0xfa, 0x4c, 0xed, 0x44, 0x6f, 0x8e, 0x54, 0x6f, 0x7a, 0xdf, 0xb2, 0xab, - 0xc1, 0xc5, 0xd9, 0x60, 0xe8, 0xd7, 0x1a, 0xbb, 0x23, 0xfb, 0xe3, 0x02, 0x8c, 0xd5, 0xe7, 0x56, - 0x6a, 0xd2, 0xd9, 0xf5, 0x6d, 0xee, 0x7b, 0x88, 0x5e, 0x70, 0xbc, 0x4b, 0x2f, 0x26, 0x2c, 0xb0, - 0x3b, 0x55, 0xcb, 0x7b, 0xf5, 0xc6, 0x5d, 0xbd, 0xd5, 0xa5, 0x78, 0x58, 0xc4, 0x3d, 0x95, 0x5d, - 0xf3, 0x03, 0xfa, 0x35, 0x0c, 0x8d, 0xe0, 0x33, 0x20, 0x6f, 0x42, 0xe1, 0x8e, 0xf0, 0x21, 0xc9, - 0xe2, 0xf3, 0xf2, 0x75, 0xce, 0x87, 0x4d, 0x82, 0x85, 0xae, 0x69, 0x20, 0x07, 0x56, 0x8a, 0x15, - 0xbe, 0x29, 0x4c, 0x86, 0x5d, 0x15, 0x5e, 0xf3, 0x0b, 0xdf, 0xac, 0x56, 0x48, 0x1d, 0x86, 0x6b, - 0xd4, 0x69, 0x9b, 0xd8, 0x51, 0xfe, 0x9c, 0xdd, 0x9b, 0x09, 0xdb, 0x5b, 0x0d, 0x77, 0xc2, 0x42, - 0xc8, 0x4c, 0xe6, 0x42, 0xde, 0x03, 0xe0, 0x56, 0xd5, 0x2e, 0x23, 0x6c, 0x5e, 0xc2, 0x9d, 0x0a, - 0x37, 0x86, 0x53, 0xac, 0x52, 0x89, 0x19, 0x79, 0x00, 0x13, 0x8b, 0xb6, 0x61, 0xde, 0x37, 0xb9, - 0xb3, 0x28, 0x56, 0x50, 0xda, 0xd9, 0x45, 0x8b, 0x19, 0xbf, 0x6d, 0xa9, 0x5c, 0x5a, 0x35, 0x09, - 0xc6, 0xea, 0xdf, 0xea, 0x87, 0x22, 0xeb, 0xf6, 0x93, 0xf1, 0x7b, 0x90, 0xf1, 0x5b, 0x86, 0x89, - 0x7b, 0xb6, 0xf3, 0xc0, 0xb4, 0xd6, 0x02, 0x3f, 0x7e, 0xb1, 0x9b, 0x46, 0xdf, 0xa3, 0x0d, 0x8e, - 0x6b, 0x04, 0x2e, 0xff, 0x5a, 0x82, 0x7c, 0x87, 0x11, 0xfc, 0x3a, 0x00, 0x7f, 0x9d, 0x8f, 0x34, - 0x83, 0x61, 0x38, 0x0f, 0xfe, 0x76, 0x1f, 0x9f, 0x06, 0xc8, 0xe1, 0x3c, 0x42, 0x62, 0x72, 0xd5, - 0xf7, 0xde, 0x18, 0xc2, 0x97, 0x02, 0x78, 0x6c, 0x80, 0xde, 0x1b, 0xb2, 0x11, 0xc0, 0xfd, 0x38, - 0x6a, 0x00, 0xd2, 0x8d, 0x18, 0xc4, 0x04, 0x11, 0x99, 0x1c, 0x44, 0x00, 0xbd, 0x94, 0x0b, 0x31, - 0x4d, 0xe2, 0x41, 0x5e, 0x8d, 0x5d, 0xd9, 0x93, 0x08, 0xb7, 0xcc, 0x1b, 0xfb, 0xd0, 0xe5, 0x6b, - 0x64, 0x27, 0x97, 0x2f, 0xf5, 0xaf, 0x17, 0x60, 0x98, 0x71, 0xab, 0x77, 0xdb, 0x6d, 0xdd, 0xd9, - 0x3c, 0x51, 0xe4, 0x83, 0x28, 0x72, 0x03, 0x4e, 0xc9, 0x2e, 0xfe, 0xcc, 0x74, 0xf5, 0x83, 0x31, - 0x05, 0x07, 0x56, 0x71, 0x02, 0x6e, 0x5b, 0xe2, 0xbc, 0xef, 0x09, 0x30, 0x9e, 0x86, 0xb8, 0x5a, - 0x92, 0x97, 0xfa, 0xd3, 0x39, 0x98, 0x88, 0x43, 0x03, 0xdd, 0xcf, 0xa5, 0xea, 0xfe, 0x0b, 0x30, - 0x24, 0x2e, 0xfd, 0x75, 0x43, 0xf8, 0x20, 0x8e, 0x6d, 0x6f, 0x4d, 0x02, 0xbe, 0xb8, 0x6e, 0x38, - 0x54, 0x37, 0xb4, 0x90, 0x80, 0xbc, 0x02, 0x23, 0xf8, 0xe3, 0x9e, 0x63, 0x7a, 0x1e, 0xe5, 0x9d, - 0x51, 0xe4, 0xf7, 0x18, 0xbc, 0xc0, 0x06, 0x47, 0x68, 0x11, 0x32, 0xf5, 0x77, 0xf2, 0x30, 0x54, - 0xef, 0xae, 0xba, 0x9b, 0xae, 0x47, 0xdb, 0xc7, 0x5c, 0x87, 0xfc, 0x63, 0x85, 0x62, 0xea, 0xb1, - 0xc2, 0xb3, 0xfe, 0xd0, 0x92, 0xce, 0xdb, 0x83, 0x8d, 0x81, 0xef, 0x47, 0x19, 0x6a, 0x51, 0x69, - 0xef, 0x5a, 0xa4, 0xfe, 0xcd, 0x3c, 0x4c, 0xf0, 0xeb, 0xe6, 0x8a, 0xe9, 0x36, 0x0f, 0xe1, 0x09, - 0xcc, 0xd1, 0xcb, 0xf4, 0x60, 0x2e, 0x1a, 0xbb, 0x78, 0x58, 0xa4, 0x7e, 0x21, 0x0f, 0xc3, 0xe5, - 0xae, 0xb7, 0x5e, 0xf6, 0x70, 0x7e, 0x7b, 0x2c, 0xf7, 0xc8, 0xff, 0x20, 0x07, 0xe3, 0xac, 0x21, - 0x2b, 0xf6, 0x03, 0x6a, 0x1d, 0xc2, 0x71, 0xbd, 0x7c, 0xec, 0x9e, 0xdf, 0xe7, 0xb1, 0xbb, 0x2f, - 0xcb, 0xc2, 0xde, 0x64, 0x89, 0x97, 0x4c, 0x9a, 0xdd, 0xa2, 0xc7, 0xfb, 0x33, 0x0e, 0xf1, 0x92, - 0xc9, 0x17, 0xc8, 0x21, 0x5c, 0x6a, 0x7e, 0x6f, 0x09, 0xe4, 0x10, 0x4e, 0x64, 0xbf, 0x37, 0x04, - 0xf2, 0xad, 0x1c, 0x0c, 0x4d, 0xdb, 0xde, 0x31, 0x1f, 0xf8, 0xe2, 0x2b, 0x8e, 0xb7, 0x9a, 0xfb, - 0x5f, 0x71, 0xbc, 0x75, 0x53, 0xfd, 0xd9, 0x3c, 0x9c, 0x11, 0x11, 0xfc, 0xc5, 0x19, 0xd8, 0xc9, - 0x74, 0x2c, 0x06, 0x5b, 0x52, 0x34, 0x27, 0xf3, 0x90, 0x10, 0xcd, 0x2f, 0x14, 0xe0, 0x0c, 0x06, - 0x1c, 0x66, 0x3b, 0xaa, 0xef, 0x01, 0x5b, 0x84, 0x34, 0xa3, 0xae, 0x03, 0x8b, 0x29, 0xae, 0x03, - 0x7f, 0xb2, 0x35, 0xf9, 0xea, 0x9a, 0xe9, 0xad, 0x77, 0x57, 0xa7, 0x9a, 0x76, 0xfb, 0xda, 0x9a, - 0xa3, 0x3f, 0x34, 0xf9, 0xa5, 0xb9, 0xde, 0xba, 0x16, 0x26, 0xd6, 0xe9, 0x98, 0x22, 0x4d, 0x4e, - 0x1d, 0x77, 0x4a, 0x8c, 0xab, 0xef, 0x74, 0xe0, 0x02, 0xdc, 0xb2, 0x4d, 0x4b, 0x78, 0xe2, 0x72, - 0x43, 0xb7, 0xbe, 0xbd, 0x35, 0x79, 0xf6, 0x7d, 0xdb, 0xb4, 0x1a, 0x71, 0x77, 0xdc, 0xbd, 0xd6, - 0x17, 0xb2, 0xd6, 0xa4, 0x6a, 0xd4, 0xff, 0x3a, 0x07, 0x4f, 0x44, 0xb5, 0xf8, 0x7b, 0xc1, 0x76, - 0xfc, 0x0b, 0x79, 0x38, 0x7b, 0x13, 0x85, 0x13, 0xb8, 0x3f, 0x9d, 0xcc, 0x5b, 0x62, 0x70, 0xa6, - 0xc8, 0xe6, 0xc4, 0xa2, 0xcc, 0x96, 0xcd, 0xc9, 0xa4, 0x2e, 0x64, 0xf3, 0x0f, 0x73, 0x70, 0x7a, - 0xb9, 0x5a, 0x99, 0xf9, 0x1e, 0x19, 0x51, 0xc9, 0xef, 0x39, 0xe6, 0x06, 0x67, 0xe2, 0x7b, 0x8e, - 0xb9, 0xe9, 0xf9, 0x95, 0x3c, 0x9c, 0xae, 0x97, 0x17, 0x17, 0xbe, 0x57, 0x66, 0xf0, 0x19, 0xd9, - 0x57, 0xd7, 0x3f, 0x04, 0x13, 0xb6, 0x80, 0xfc, 0x99, 0x77, 0xaf, 0x67, 0xfb, 0xf0, 0x26, 0x85, - 0x72, 0xcc, 0xa7, 0xee, 0x43, 0x11, 0x0a, 0xd3, 0xfc, 0x08, 0xf5, 0x31, 0xd7, 0xfc, 0xbf, 0x57, - 0x82, 0xe1, 0xdb, 0xdd, 0x55, 0x2a, 0x5c, 0xba, 0x1e, 0xeb, 0x93, 0xdf, 0xeb, 0x30, 0x2c, 0xc4, - 0x80, 0x37, 0x1c, 0x52, 0xc8, 0x49, 0x11, 0x42, 0x88, 0x47, 0xf5, 0x92, 0x89, 0xc8, 0x45, 0x28, - 0xde, 0xa5, 0xce, 0xaa, 0xfc, 0x1a, 0xfb, 0x21, 0x75, 0x56, 0x35, 0x84, 0x92, 0x85, 0xf0, 0xa1, - 0x49, 0xb9, 0x56, 0xc5, 0x74, 0x47, 0xe2, 0xd2, 0x10, 0xf3, 0x37, 0x05, 0xde, 0xa2, 0x7a, 0xc7, - 0xe4, 0x89, 0x92, 0xe4, 0x48, 0x10, 0xf1, 0x92, 0x64, 0x09, 0x4e, 0xc9, 0xee, 0x82, 0x3c, 0xd7, - 0xcf, 0x60, 0x0a, 0xbb, 0xb4, 0x2c, 0x3f, 0xc9, 0xa2, 0xe4, 0x1d, 0x18, 0xf1, 0x81, 0xe8, 0xf8, - 0x38, 0x14, 0x26, 0x98, 0x08, 0x58, 0xc5, 0xf2, 0x01, 0x44, 0x0a, 0xc8, 0x0c, 0xf0, 0x12, 0x03, - 0x52, 0x18, 0xc4, 0x1c, 0x49, 0x23, 0x05, 0xc8, 0x2b, 0xc8, 0x00, 0x1f, 0x47, 0xa1, 0xc3, 0xd4, - 0x30, 0x3e, 0x55, 0xc6, 0x0b, 0x20, 0x47, 0xc0, 0xf9, 0x83, 0xf4, 0x08, 0x19, 0x59, 0x06, 0x08, - 0x1d, 0x5b, 0x44, 0xd8, 0x8f, 0x3d, 0xbb, 0xdc, 0x48, 0x2c, 0xe4, 0x9b, 0xbc, 0xd1, 0xfd, 0xdc, - 0xe4, 0xa9, 0x3f, 0x53, 0x80, 0xe1, 0x72, 0xa7, 0x13, 0x0c, 0x85, 0x17, 0xa1, 0x54, 0xee, 0x74, - 0xee, 0x68, 0x55, 0x39, 0x01, 0x80, 0xde, 0xe9, 0x34, 0xba, 0x8e, 0x29, 0x7b, 0x52, 0x73, 0x22, - 0x32, 0x03, 0xa3, 0xe5, 0x4e, 0xa7, 0xd6, 0x5d, 0x6d, 0x99, 0x4d, 0x29, 0x7f, 0x19, 0xcf, 0xf0, - 0xd8, 0xe9, 0x34, 0x3a, 0x88, 0x89, 0x27, 0xb1, 0x8b, 0x96, 0x21, 0x9f, 0xc7, 0x60, 0x59, 0x22, - 0x7d, 0x16, 0x4f, 0xd0, 0xa3, 0x06, 0xa1, 0xff, 0xc3, 0xb6, 0x4d, 0x05, 0x44, 0x3c, 0x45, 0xc2, - 0x45, 0x3f, 0xb1, 0x05, 0xab, 0x28, 0x91, 0x26, 0x2b, 0x64, 0x49, 0x3e, 0x09, 0x03, 0xe5, 0x4e, - 0x47, 0xba, 0xad, 0x42, 0xc7, 0x36, 0x56, 0x2a, 0x9e, 0xa1, 0x50, 0x90, 0x89, 0xcf, 0x12, 0xf7, - 0xdb, 0xb6, 0xe3, 0xe1, 0x90, 0x1a, 0x0d, 0x3f, 0xcb, 0xbf, 0x10, 0xb7, 0xe5, 0xf8, 0x34, 0x5a, - 0xb4, 0xcc, 0x85, 0xb7, 0x60, 0x2c, 0xda, 0xe2, 0x3d, 0xe5, 0x69, 0xf8, 0x6e, 0x0e, 0xa5, 0x72, - 0xcc, 0x9f, 0x13, 0xbc, 0x0c, 0x85, 0x72, 0xa7, 0x23, 0x26, 0xb5, 0xd3, 0x29, 0x9d, 0x1a, 0x8f, - 0x3e, 0x50, 0xee, 0x74, 0xfc, 0x4f, 0x3f, 0xe6, 0xef, 0x92, 0xf6, 0xf5, 0xe9, 0xdf, 0xe2, 0x9f, - 0x7e, 0xbc, 0xdf, 0x0c, 0xa9, 0xbf, 0x56, 0x80, 0xf1, 0x72, 0xa7, 0x73, 0x92, 0xdf, 0xe1, 0xb0, - 0x62, 0x1c, 0xbc, 0x04, 0x20, 0xcd, 0xb1, 0x03, 0xc1, 0xab, 0xc9, 0x61, 0x69, 0x7e, 0x55, 0x72, - 0x9a, 0x44, 0xe4, 0xab, 0xdf, 0xe0, 0x9e, 0xd4, 0xef, 0x0b, 0x05, 0x9c, 0xf8, 0x8e, 0x7b, 0xbc, - 0xb6, 0x8f, 0x4a, 0xb7, 0x89, 0x3e, 0x28, 0xed, 0xa9, 0x0f, 0xfe, 0x4e, 0x64, 0xf0, 0x60, 0xbe, - 0x80, 0x93, 0x5e, 0xe8, 0x3f, 0x90, 0x6d, 0x3d, 0x26, 0x0b, 0x53, 0x04, 0x91, 0xf2, 0x73, 0xa6, - 0x89, 0x90, 0x66, 0x4d, 0x86, 0x6a, 0x98, 0x86, 0x16, 0xa3, 0xf5, 0xfb, 0x70, 0x60, 0x4f, 0x7d, - 0xb8, 0x95, 0xc7, 0xb0, 0x05, 0x41, 0x48, 0xb4, 0x83, 0x6f, 0x51, 0xae, 0x01, 0x70, 0xf7, 0x85, - 0xc0, 0x3f, 0x7f, 0x94, 0x47, 0x3f, 0xe2, 0xa9, 0xd4, 0x44, 0xf4, 0xa3, 0x90, 0x24, 0x70, 0x77, - 0x2a, 0xa4, 0xba, 0x3b, 0x5d, 0x85, 0x41, 0x4d, 0xdf, 0x78, 0xb7, 0x4b, 0x9d, 0x4d, 0x61, 0x13, - 0xf1, 0x88, 0xa3, 0xfa, 0x46, 0xe3, 0x87, 0x18, 0x50, 0x0b, 0xd0, 0x44, 0x0d, 0xe2, 0x5e, 0x48, - 0x6e, 0x25, 0xfc, 0xa0, 0x3d, 0x88, 0x76, 0xb1, 0x1f, 0x45, 0x27, 0x6f, 0x40, 0xa1, 0x7c, 0xaf, - 0x2e, 0x24, 0x1b, 0x74, 0x6d, 0xf9, 0x5e, 0x5d, 0xc8, 0x2b, 0xb3, 0xec, 0xbd, 0xba, 0xfa, 0x85, - 0x3c, 0x90, 0x24, 0x25, 0x79, 0x15, 0x86, 0x10, 0xba, 0xc6, 0x74, 0x46, 0xce, 0xc1, 0xbb, 0xe1, - 0x36, 0x1c, 0x84, 0x46, 0x2c, 0x44, 0x9f, 0x94, 0xbc, 0x8e, 0x59, 0xce, 0x45, 0x16, 0xc8, 0x48, - 0x0e, 0xde, 0x0d, 0xd7, 0xcf, 0x0b, 0x1e, 0x4b, 0x72, 0x2e, 0x88, 0xd1, 0xb8, 0xbc, 0x57, 0x9f, - 0xb7, 0x5d, 0x4f, 0x88, 0x9a, 0x1b, 0x97, 0x1b, 0x2e, 0x26, 0x7f, 0x8e, 0x18, 0x97, 0x9c, 0x0c, - 0x13, 0xd8, 0xdd, 0xab, 0xf3, 0x17, 0x62, 0x86, 0x66, 0xb7, 0x7c, 0xab, 0x94, 0x27, 0xb0, 0xdb, - 0x70, 0x1b, 0xfc, 0x75, 0x99, 0x81, 0xe9, 0xd5, 0x23, 0x09, 0xec, 0x22, 0xa5, 0xd4, 0x9f, 0x1c, - 0x84, 0x89, 0x8a, 0xee, 0xe9, 0xab, 0xba, 0x4b, 0xa5, 0x2d, 0xf9, 0xb8, 0x0f, 0xf3, 0x3f, 0x47, - 0x92, 0x83, 0xb1, 0x9a, 0xf2, 0x35, 0xf1, 0x02, 0xe4, 0xcd, 0x90, 0x6f, 0x90, 0x5e, 0x58, 0xce, - 0x57, 0xb8, 0xda, 0xe8, 0x08, 0xb0, 0x96, 0x20, 0x24, 0x2f, 0xc0, 0xb0, 0x0f, 0x63, 0xbb, 0x88, - 0x42, 0xa8, 0x33, 0xc6, 0x2a, 0xdb, 0x44, 0x68, 0x32, 0x9a, 0xbc, 0x0e, 0x23, 0xfe, 0x4f, 0xc9, - 0x3e, 0xe7, 0xc9, 0x17, 0x57, 0x13, 0x5b, 0x30, 0x99, 0x54, 0x2e, 0x8a, 0xf3, 0x5b, 0x7f, 0xa4, - 0x68, 0x2c, 0xbf, 0x61, 0x84, 0x94, 0xfc, 0x10, 0x8c, 0xf9, 0xbf, 0xc5, 0xae, 0x83, 0x7b, 0x1f, - 0xbe, 0x10, 0x64, 0x6f, 0x8f, 0x89, 0x75, 0x2a, 0x4a, 0xce, 0xf7, 0x1f, 0x4f, 0xfa, 0x29, 0xfb, - 0x8c, 0xd5, 0xe4, 0xf6, 0x23, 0x56, 0x01, 0xa9, 0xc2, 0x29, 0x1f, 0x12, 0x6a, 0xe8, 0x40, 0xb8, - 0xed, 0x34, 0x56, 0x1b, 0xa9, 0x4a, 0x9a, 0x2c, 0x45, 0x5a, 0x70, 0x31, 0x02, 0x34, 0xdc, 0x75, - 0xf3, 0xbe, 0x27, 0xf6, 0x8c, 0x22, 0xfc, 0xb7, 0xc8, 0xd1, 0x1a, 0x70, 0xe5, 0x34, 0x7e, 0xb2, - 0xe5, 0x68, 0x62, 0xb6, 0x9e, 0xdc, 0x48, 0x1d, 0xce, 0xf8, 0xf8, 0x9b, 0x33, 0xb5, 0x9a, 0x63, - 0xbf, 0x4f, 0x9b, 0x5e, 0xb5, 0x22, 0xf6, 0xdc, 0x18, 0x16, 0xd2, 0x58, 0x6d, 0xac, 0x35, 0x3b, - 0x4c, 0x29, 0x18, 0x2e, 0xca, 0x3c, 0xb5, 0x30, 0xb9, 0x0b, 0x67, 0x25, 0xb8, 0x94, 0x09, 0x1e, - 0xc2, 0x43, 0x01, 0xc1, 0x35, 0x3d, 0x19, 0x7c, 0x7a, 0x71, 0xf2, 0x16, 0x8c, 0xfa, 0x08, 0x7e, - 0x15, 0x39, 0x8c, 0x57, 0x91, 0x38, 0x24, 0x8d, 0xd5, 0x46, 0xfc, 0x21, 0x73, 0x94, 0x58, 0xd6, - 0xa8, 0x95, 0xcd, 0x0e, 0x15, 0x6e, 0xc1, 0xbe, 0x46, 0x79, 0x9b, 0x9d, 0x54, 0x65, 0x64, 0xa4, - 0xe4, 0x9d, 0x50, 0xa3, 0x96, 0x1d, 0x73, 0xcd, 0xe4, 0xdb, 0x71, 0xff, 0xed, 0xf2, 0x6a, 0xc3, - 0x46, 0x60, 0x9a, 0x7e, 0x70, 0xf2, 0x0b, 0x65, 0x38, 0x9d, 0xa2, 0x63, 0x7b, 0xda, 0x31, 0x7e, - 0x31, 0x1f, 0x36, 0xe2, 0x98, 0x6f, 0x1b, 0xa7, 0x61, 0xd0, 0xff, 0x12, 0x61, 0x3c, 0x28, 0x59, - 0x43, 0x33, 0xce, 0xc3, 0xc7, 0x47, 0xc4, 0x71, 0xcc, 0xb7, 0x92, 0x87, 0x21, 0x8e, 0x6f, 0xe7, - 0x42, 0x71, 0x1c, 0xf3, 0xed, 0xe5, 0x4f, 0x14, 0xc3, 0x39, 0xe9, 0x64, 0x8f, 0x79, 0x58, 0x66, - 0x72, 0xe8, 0x4c, 0x5b, 0xda, 0xc3, 0x1b, 0x62, 0x59, 0x35, 0x07, 0xf6, 0xa7, 0x9a, 0xe4, 0x2d, - 0x18, 0xae, 0xd9, 0xae, 0xb7, 0xe6, 0x50, 0xb7, 0x16, 0xa4, 0xaf, 0xc0, 0xf7, 0xe7, 0x1d, 0x01, - 0x6e, 0x74, 0x22, 0xb3, 0xbf, 0x4c, 0xae, 0xfe, 0xe3, 0x42, 0x42, 0x1b, 0xb8, 0xe1, 0x7a, 0x2c, - 0xb5, 0xe1, 0x10, 0x86, 0x3a, 0xb9, 0x1e, 0xae, 0x82, 0xdc, 0xc2, 0xef, 0x97, 0x62, 0x73, 0xae, - 0x0a, 0x03, 0x3f, 0x4a, 0x42, 0xbe, 0x1f, 0xce, 0x47, 0x00, 0x35, 0xdd, 0xd1, 0xdb, 0xd4, 0x0b, - 0x53, 0x85, 0x62, 0xb4, 0x35, 0xbf, 0x74, 0xa3, 0x13, 0xa0, 0xe5, 0xf4, 0xa3, 0x19, 0x1c, 0x24, - 0xd5, 0x1a, 0xd8, 0x83, 0x9f, 0xf6, 0x57, 0x0b, 0xa1, 0xa1, 0x13, 0x8d, 0x9a, 0xac, 0x51, 0xb7, - 0xdb, 0xf2, 0x1e, 0xdf, 0x0e, 0xde, 0x5f, 0x4e, 0x9a, 0x79, 0x18, 0x2f, 0xdf, 0xbf, 0x4f, 0x9b, - 0x9e, 0x1f, 0x0c, 0xde, 0x15, 0x71, 0x32, 0xf9, 0xc6, 0x43, 0xa0, 0x44, 0x70, 0x6f, 0xb9, 0x5f, - 0xe3, 0xc5, 0xd4, 0x7f, 0x52, 0x04, 0x25, 0x30, 0xfc, 0x83, 0xf7, 0x8a, 0x47, 0xb8, 0xc8, 0x7e, - 0x24, 0x7a, 0xc5, 0x84, 0x53, 0xa1, 0x30, 0xc4, 0x43, 0x31, 0x91, 0xbc, 0x7e, 0x32, 0xce, 0x2c, - 0x24, 0xe4, 0x7b, 0x89, 0x0b, 0x62, 0x2f, 0x41, 0xc2, 0xf7, 0xa0, 0x0d, 0x97, 0xb3, 0xd0, 0x92, - 0x5c, 0xc9, 0x97, 0x72, 0x70, 0xc6, 0xef, 0x94, 0xe5, 0x55, 0x66, 0x54, 0xcf, 0xd8, 0x5d, 0x2b, - 0x78, 0x45, 0xf5, 0x46, 0x76, 0x75, 0xbc, 0x93, 0xa6, 0xd2, 0x0a, 0xf3, 0x96, 0x04, 0x11, 0x61, - 0x02, 0x85, 0xb0, 0x91, 0xa6, 0xd1, 0x44, 0x22, 0x2d, 0xb5, 0xde, 0x0b, 0x37, 0xe1, 0x89, 0x4c, - 0x96, 0x3b, 0x19, 0xb1, 0xfd, 0xb2, 0x11, 0xfb, 0xdf, 0xe6, 0xc2, 0x89, 0x28, 0x26, 0x24, 0x32, - 0x05, 0x10, 0x82, 0xc4, 0xb6, 0x16, 0x1f, 0x69, 0x85, 0x42, 0xd3, 0x24, 0x0a, 0xb2, 0x0c, 0x25, - 0x21, 0x16, 0x9e, 0x96, 0xfb, 0x13, 0x3b, 0xf4, 0xc2, 0x94, 0x2c, 0x07, 0xdc, 0xb2, 0x8a, 0x6f, - 0x16, 0x6c, 0x2e, 0xbc, 0x0e, 0xc3, 0xfb, 0xfd, 0xae, 0x2f, 0x15, 0x80, 0xc8, 0x7b, 0xd0, 0x23, - 0x34, 0xd0, 0x8f, 0xf1, 0x14, 0x76, 0x05, 0x06, 0xd9, 0x27, 0x60, 0xa2, 0x1a, 0x29, 0x30, 0x75, - 0x57, 0xc0, 0xb4, 0x00, 0x1b, 0x46, 0x85, 0x1b, 0x48, 0x8f, 0x0a, 0xa7, 0xfe, 0x74, 0x01, 0xce, - 0xc9, 0x1d, 0x52, 0xa1, 0x98, 0xeb, 0xe2, 0xa4, 0x53, 0x3e, 0xc4, 0x4e, 0x51, 0xa1, 0xc4, 0xb7, - 0x1e, 0x22, 0xe9, 0x08, 0x3f, 0x16, 0x42, 0x88, 0x26, 0x30, 0xea, 0xff, 0x9c, 0x87, 0xd1, 0xc0, - 0xbc, 0xd3, 0x1d, 0xf7, 0x31, 0xee, 0x8e, 0x4f, 0xc1, 0x28, 0xc6, 0xf5, 0x6a, 0x53, 0x8b, 0xc7, - 0xbe, 0xea, 0x97, 0xb2, 0x04, 0xf9, 0x08, 0x91, 0x10, 0x2e, 0x42, 0xc8, 0xb4, 0x9f, 0x5b, 0x7e, - 0x52, 0xb4, 0x35, 0x6e, 0xf6, 0x71, 0xb8, 0xfa, 0x97, 0x0b, 0x30, 0xe2, 0x4b, 0x79, 0xda, 0x3c, - 0xae, 0xf7, 0x3c, 0x47, 0x2b, 0xe4, 0x6b, 0x00, 0x35, 0xdb, 0xf1, 0xf4, 0xd6, 0x52, 0xa8, 0xf9, - 0x78, 0x40, 0xda, 0x41, 0x28, 0x2f, 0x23, 0x91, 0xe0, 0xfa, 0x15, 0x9a, 0xd5, 0x7c, 0x62, 0xe2, - 0xeb, 0x57, 0x00, 0xd5, 0x24, 0x0a, 0xf5, 0xb7, 0xf2, 0x30, 0xee, 0x77, 0xd2, 0xec, 0x23, 0xda, - 0xec, 0x3e, 0xce, 0x73, 0x53, 0x54, 0xda, 0xfd, 0x3b, 0x4a, 0x5b, 0xfd, 0xbf, 0xa4, 0x89, 0x64, - 0xa6, 0x65, 0x9f, 0x4c, 0x24, 0xff, 0x3a, 0x74, 0x5c, 0xfd, 0x91, 0x02, 0x9c, 0xf1, 0xa5, 0x3e, - 0xd7, 0xb5, 0xf0, 0x68, 0x61, 0x46, 0x6f, 0xb5, 0x1e, 0xe7, 0xdd, 0xf8, 0xb0, 0x2f, 0x88, 0x65, - 0x11, 0x28, 0x53, 0x24, 0xe7, 0xbc, 0x2f, 0xc0, 0x0d, 0xdb, 0x34, 0x34, 0x99, 0x88, 0xbc, 0x03, - 0x23, 0xfe, 0xcf, 0xb2, 0xb3, 0xe6, 0x6f, 0xc1, 0xf1, 0xa2, 0x20, 0x28, 0xa4, 0x3b, 0x91, 0xe8, - 0x1a, 0x91, 0x02, 0xea, 0x17, 0x06, 0xe0, 0xc2, 0x3d, 0xd3, 0x32, 0xec, 0x0d, 0xd7, 0xcf, 0xed, - 0x7a, 0xec, 0x0f, 0xca, 0x8e, 0x3a, 0xa7, 0xeb, 0xbb, 0x70, 0x36, 0x2e, 0x52, 0x27, 0x88, 0xb8, - 0x2f, 0x7a, 0x67, 0x83, 0x13, 0x34, 0xfc, 0x2c, 0xaf, 0xe2, 0xb6, 0x4d, 0x4b, 0x2f, 0x19, 0x4f, - 0x13, 0x3b, 0xb0, 0x9b, 0x34, 0xb1, 0xcf, 0x43, 0xa9, 0x62, 0xb7, 0x75, 0xd3, 0x8f, 0xb3, 0x84, - 0xa3, 0x38, 0xa8, 0x17, 0x31, 0x9a, 0xa0, 0x60, 0xfc, 0x45, 0xc5, 0xd8, 0x65, 0x43, 0x21, 0x7f, - 0xbf, 0x00, 0xb3, 0xd2, 0x34, 0x99, 0x88, 0xd8, 0x30, 0x2a, 0xaa, 0x13, 0x77, 0x63, 0x80, 0x9b, - 0xa7, 0x57, 0x7c, 0x19, 0x65, 0xab, 0xd5, 0x54, 0xa4, 0x1c, 0xdf, 0x46, 0xf1, 0xec, 0xb5, 0xe2, - 0x63, 0xf8, 0x2d, 0x99, 0x16, 0xe5, 0x2f, 0x09, 0x01, 0x27, 0x99, 0xe1, 0xa4, 0x10, 0x70, 0x96, - 0x91, 0x89, 0xc8, 0x2c, 0x9c, 0xc2, 0xb8, 0xe8, 0xc1, 0x56, 0x8a, 0xa9, 0xc4, 0x08, 0x1a, 0x95, - 0x78, 0xe5, 0xc2, 0x43, 0xa9, 0xb3, 0x8f, 0x6b, 0x34, 0x05, 0x5a, 0x4b, 0x96, 0x20, 0x4f, 0x40, - 0x61, 0x69, 0xa1, 0x8c, 0x77, 0x35, 0x83, 0x3c, 0x27, 0x99, 0xd5, 0xd2, 0x35, 0x06, 0xbb, 0xf0, - 0x69, 0x20, 0xc9, 0xcf, 0xd9, 0xd3, 0x7d, 0xcc, 0xdf, 0x97, 0xb6, 0x7c, 0xc7, 0xdd, 0xa3, 0xe6, - 0x30, 0x26, 0xc2, 0x48, 0x3a, 0xc0, 0xfe, 0x0f, 0x33, 0x1d, 0x60, 0xe9, 0x50, 0xd3, 0x01, 0xaa, - 0xbf, 0x9c, 0x83, 0x53, 0x89, 0xdc, 0x01, 0xe4, 0x65, 0x00, 0x0e, 0x91, 0x62, 0xb4, 0x62, 0x08, - 0xa1, 0x30, 0x9f, 0x80, 0x58, 0x1e, 0x43, 0x32, 0x72, 0x0d, 0x06, 0xf9, 0x2f, 0x11, 0xa5, 0x2c, - 0x59, 0xa4, 0xdb, 0x35, 0x0d, 0x2d, 0x20, 0x0a, 0x6b, 0xc1, 0x1b, 0xc9, 0x42, 0x6a, 0x11, 0x6f, - 0xb3, 0x13, 0xd4, 0xc2, 0xc8, 0xd4, 0x9f, 0xcc, 0xc3, 0x48, 0xd0, 0xe0, 0xb2, 0x71, 0x54, 0x3a, - 0x57, 0x12, 0x69, 0x18, 0x0a, 0x3b, 0xa5, 0x61, 0x88, 0xcd, 0xb7, 0x22, 0xef, 0xc2, 0xe1, 0xbd, - 0xca, 0xfa, 0x72, 0x1e, 0xc6, 0x83, 0x5a, 0x8f, 0xf0, 0xf2, 0xeb, 0x23, 0x24, 0x92, 0x2f, 0xe5, - 0x40, 0x99, 0x36, 0x5b, 0x2d, 0xd3, 0x5a, 0xab, 0x5a, 0xf7, 0x6d, 0xa7, 0x8d, 0x13, 0xe2, 0xd1, - 0x1d, 0xe1, 0xaa, 0x7f, 0x36, 0x07, 0xa7, 0x44, 0x83, 0x66, 0x74, 0xc7, 0x38, 0xba, 0xf3, 0xb1, - 0x78, 0x4b, 0x8e, 0x4e, 0x5f, 0xd4, 0x6f, 0xe4, 0x01, 0x16, 0xec, 0xe6, 0x83, 0x63, 0xfe, 0xa8, - 0xeb, 0x4d, 0x28, 0x71, 0xb7, 0x78, 0xa1, 0xb1, 0xa7, 0xc4, 0xe3, 0x25, 0xf6, 0x69, 0x1c, 0x31, - 0x3d, 0x21, 0xe6, 0xe3, 0x12, 0xf7, 0xac, 0x57, 0x72, 0x9a, 0x28, 0xc2, 0x2a, 0x65, 0x74, 0x62, - 0xc1, 0x08, 0x2a, 0x65, 0xb0, 0x68, 0xa5, 0xdb, 0x5b, 0x93, 0xc5, 0x96, 0xdd, 0x7c, 0xa0, 0x21, - 0xbd, 0xfa, 0x2f, 0x73, 0x5c, 0x76, 0xc7, 0xfc, 0x69, 0xaa, 0xff, 0xf9, 0xc5, 0x3d, 0x7e, 0xfe, - 0x9f, 0xcb, 0xc1, 0x19, 0x8d, 0x36, 0xed, 0x87, 0xd4, 0xd9, 0x9c, 0xb1, 0x0d, 0x7a, 0x93, 0x5a, - 0xd4, 0x39, 0xaa, 0x11, 0xf5, 0xdb, 0x98, 0xb7, 0x26, 0x6c, 0xcc, 0x1d, 0x97, 0x1a, 0xc7, 0x27, - 0xa7, 0x90, 0xfa, 0xab, 0x03, 0xa0, 0xa4, 0x5a, 0xbd, 0xc7, 0xd6, 0x9c, 0xcb, 0xdc, 0xca, 0x14, - 0x0f, 0x6b, 0x2b, 0xd3, 0xbf, 0xb7, 0xad, 0x4c, 0x69, 0xaf, 0x5b, 0x99, 0x81, 0xdd, 0x6c, 0x65, - 0xda, 0xf1, 0xad, 0xcc, 0x20, 0x6e, 0x65, 0x5e, 0xee, 0xb9, 0x95, 0x99, 0xb5, 0x8c, 0x7d, 0x6e, - 0x64, 0x8e, 0x6d, 0xbe, 0xeb, 0xfd, 0xec, 0xc0, 0xae, 0xb0, 0x49, 0xb1, 0x69, 0x3b, 0x06, 0x35, - 0xc4, 0xc6, 0x0b, 0x4f, 0xfd, 0x1d, 0x01, 0xd3, 0x02, 0x6c, 0x22, 0x79, 0xf8, 0xe8, 0x6e, 0x92, - 0x87, 0x1f, 0xc2, 0xfe, 0xeb, 0x8b, 0x79, 0x38, 0x35, 0x43, 0x1d, 0x8f, 0xc7, 0xa2, 0x3d, 0x0c, - 0x97, 0xb8, 0x32, 0x8c, 0x4b, 0x0c, 0xd1, 0x22, 0xcf, 0x87, 0x6e, 0x7e, 0x4d, 0xea, 0x78, 0x71, - 0x2f, 0xc1, 0x38, 0x3d, 0xab, 0xde, 0x4f, 0xe0, 0x27, 0xc6, 0x6e, 0x50, 0xbd, 0x0f, 0xe7, 0x82, - 0x34, 0xc5, 0x2f, 0x2d, 0xa0, 0x97, 0x72, 0xf2, 0x15, 0xf7, 0x9e, 0x93, 0x4f, 0xfd, 0xa5, 0x1c, - 0x5c, 0xd6, 0xa8, 0x45, 0x37, 0xf4, 0xd5, 0x16, 0x95, 0x9a, 0x25, 0x56, 0x06, 0x36, 0x6b, 0x98, - 0x6e, 0x5b, 0xf7, 0x9a, 0xeb, 0x07, 0x92, 0xd1, 0x1c, 0x8c, 0xc8, 0xf3, 0xd7, 0x1e, 0xe6, 0xb6, - 0x48, 0x39, 0xf5, 0x57, 0x8b, 0x30, 0x30, 0x6d, 0x7b, 0xb7, 0xec, 0x03, 0x26, 0x89, 0x0c, 0xa7, - 0xfc, 0xfc, 0x1e, 0xce, 0x7a, 0x3e, 0x89, 0x95, 0x4b, 0x79, 0x33, 0xd0, 0x85, 0x74, 0xd5, 0x4e, - 0xe4, 0x17, 0xf1, 0xc9, 0xf6, 0x98, 0x1e, 0xf2, 0x55, 0x18, 0xc2, 0x10, 0x32, 0xd2, 0x69, 0x2c, - 0x3a, 0x68, 0x7b, 0x0c, 0x18, 0xaf, 0x23, 0x24, 0x25, 0xdf, 0x1f, 0x09, 0x9e, 0x5b, 0x3a, 0x78, - 0x3a, 0x49, 0x39, 0x8e, 0xee, 0xcb, 0xfc, 0x22, 0x0f, 0xdb, 0x24, 0xa5, 0xde, 0xc1, 0x53, 0x94, - 0x58, 0x93, 0x02, 0xc2, 0x43, 0x4c, 0xf5, 0x38, 0x03, 0xa3, 0xd3, 0xb6, 0x27, 0x39, 0x03, 0x0f, - 0x85, 0x6f, 0x49, 0x99, 0xe4, 0xd3, 0x3d, 0x81, 0xa3, 0x65, 0xd4, 0x3f, 0x2e, 0xc2, 0x88, 0xff, - 0xf3, 0x88, 0x74, 0xe7, 0x45, 0x28, 0xcd, 0xdb, 0x52, 0xf6, 0x11, 0x74, 0x20, 0x5e, 0xb7, 0xdd, - 0x98, 0x67, 0xb4, 0x20, 0x62, 0x52, 0x5f, 0xb2, 0x0d, 0xd9, 0xfd, 0x1d, 0xa5, 0x6e, 0xd9, 0x46, - 0xe2, 0x0d, 0x72, 0x40, 0x48, 0x2e, 0x43, 0x11, 0x5f, 0x0e, 0x48, 0x07, 0xf9, 0xb1, 0xd7, 0x02, - 0x88, 0x97, 0xb4, 0xb2, 0xb4, 0x57, 0xad, 0x1c, 0xd8, 0xaf, 0x56, 0x0e, 0x1e, 0xae, 0x56, 0xbe, - 0x07, 0x23, 0x58, 0x93, 0x9f, 0xbc, 0x70, 0xe7, 0x85, 0xf5, 0x09, 0xb1, 0xf6, 0x8d, 0xf2, 0x76, - 0x8b, 0x14, 0x86, 0xb8, 0xe4, 0x45, 0x58, 0xc5, 0x74, 0x17, 0x0e, 0xb0, 0x9d, 0xfe, 0xc7, 0x39, - 0x18, 0xb8, 0x63, 0x3d, 0xb0, 0xec, 0x8d, 0x83, 0x69, 0xdc, 0xcb, 0x30, 0x2c, 0xd8, 0x48, 0xab, - 0x0b, 0x3e, 0x2b, 0xef, 0x72, 0x70, 0x03, 0x39, 0x69, 0x32, 0x15, 0x79, 0x2b, 0x28, 0x84, 0x8f, - 0x83, 0x0a, 0x61, 0xfe, 0x1e, 0xbf, 0x50, 0x33, 0x9a, 0xc0, 0x43, 0x26, 0x27, 0x17, 0xa1, 0x58, - 0x61, 0x4d, 0x95, 0x02, 0xf9, 0xb2, 0xa6, 0x68, 0x08, 0x55, 0xbf, 0x58, 0x84, 0xb1, 0xd8, 0xc1, - 0xd7, 0xf3, 0x30, 0x24, 0x0e, 0x9e, 0x4c, 0x3f, 0xa3, 0x08, 0x3e, 0x1e, 0x0a, 0x80, 0xda, 0x20, - 0xff, 0xb3, 0x6a, 0x90, 0xef, 0x83, 0x01, 0xdb, 0xc5, 0x45, 0x11, 0xbf, 0x65, 0x2c, 0x1c, 0x42, - 0xcb, 0x75, 0xd6, 0x76, 0x3e, 0x38, 0x04, 0x89, 0xac, 0x91, 0xb6, 0x8b, 0x9f, 0x76, 0x03, 0x86, - 0x74, 0xd7, 0xa5, 0x5e, 0xc3, 0xd3, 0xd7, 0xe4, 0x24, 0x23, 0x01, 0x50, 0x1e, 0x1d, 0x08, 0x5c, - 0xd1, 0xd7, 0xc8, 0xa7, 0x61, 0xb4, 0xe9, 0x50, 0x5c, 0x36, 0xf5, 0x16, 0x6b, 0xa5, 0x64, 0xd6, - 0x46, 0x10, 0xf2, 0xfd, 0x49, 0x88, 0xa8, 0x1a, 0xe4, 0x2e, 0x8c, 0x8a, 0xcf, 0xe1, 0x9e, 0xfb, - 0x38, 0xd0, 0xc6, 0xc2, 0x65, 0x8c, 0x8b, 0x84, 0xfb, 0xee, 0x8b, 0x07, 0x1c, 0x32, 0xb9, 0xcc, - 0xd7, 0x90, 0x48, 0xc9, 0x32, 0x90, 0x0d, 0xba, 0xda, 0xd0, 0xbb, 0xde, 0x3a, 0xab, 0x8b, 0xc7, - 0xc8, 0x17, 0xd9, 0x40, 0xf1, 0xd5, 0x43, 0x12, 0x2b, 0x3f, 0x06, 0xd9, 0xa0, 0xab, 0xe5, 0x08, - 0x92, 0xdc, 0x83, 0xb3, 0xc9, 0x22, 0xec, 0x93, 0xf9, 0xe5, 0xc0, 0xb3, 0xdb, 0x5b, 0x93, 0x93, - 0xa9, 0x04, 0x12, 0xdb, 0xd3, 0x09, 0xb6, 0x55, 0xe3, 0x56, 0x71, 0x70, 0x60, 0x62, 0x50, 0x1b, - 0x63, 0x65, 0x7d, 0x13, 0xd2, 0x34, 0xd4, 0xdf, 0xcd, 0x31, 0x53, 0x91, 0x7d, 0x10, 0xa6, 0x43, - 0x67, 0xba, 0xde, 0xde, 0xa3, 0xae, 0xb7, 0xc3, 0xc4, 0xa5, 0x25, 0xb7, 0xc7, 0xec, 0xaa, 0x09, - 0x2c, 0x99, 0x82, 0x92, 0x21, 0x9f, 0x9a, 0x9d, 0x8b, 0x76, 0x82, 0x5f, 0x8f, 0x26, 0xa8, 0xc8, - 0x15, 0x28, 0xb2, 0x25, 0x2b, 0xbe, 0x65, 0x96, 0xad, 0x0b, 0x0d, 0x29, 0xd4, 0x1f, 0xce, 0xc3, - 0x88, 0xf4, 0x35, 0xd7, 0x0f, 0xf4, 0x39, 0x6f, 0xec, 0xae, 0x99, 0xbe, 0xd3, 0x0b, 0xee, 0xa5, - 0xfc, 0x26, 0xdf, 0x08, 0x44, 0xb1, 0xab, 0x0b, 0x29, 0x21, 0x98, 0x57, 0xc5, 0x87, 0x96, 0x76, - 0xbf, 0x7d, 0x64, 0xf4, 0xb7, 0x8a, 0x83, 0xf9, 0x89, 0xc2, 0xad, 0xe2, 0x60, 0x71, 0xa2, 0x1f, - 0x83, 0x79, 0x61, 0xfc, 0x6c, 0xbe, 0x37, 0xb7, 0xee, 0x9b, 0x6b, 0xc7, 0xfc, 0xed, 0xc8, 0xe1, - 0x06, 0x3a, 0x8b, 0xc9, 0xe6, 0x98, 0x3f, 0x24, 0xf9, 0x50, 0x65, 0x73, 0x92, 0xe8, 0x54, 0xc8, - 0xe6, 0x9f, 0xe4, 0x40, 0x49, 0x95, 0x4d, 0xf9, 0x88, 0xfc, 0x20, 0x0e, 0x2f, 0xdd, 0xe9, 0x1f, - 0xe6, 0xe1, 0x54, 0xd5, 0xf2, 0xe8, 0x1a, 0xdf, 0x31, 0x1e, 0xf3, 0xa9, 0xe2, 0x36, 0x0c, 0x4b, - 0x1f, 0x23, 0xfa, 0xfc, 0xc9, 0x60, 0x3f, 0x1e, 0xa2, 0x32, 0x38, 0xc9, 0xa5, 0x0f, 0xef, 0x25, - 0x4e, 0x5c, 0xc8, 0xc7, 0x7c, 0xce, 0x39, 0x1e, 0x42, 0x3e, 0xe6, 0x93, 0xd7, 0x47, 0x54, 0xc8, - 0xff, 0x47, 0x0e, 0x4e, 0xa7, 0x54, 0x4e, 0x2e, 0xc3, 0x40, 0xbd, 0xbb, 0x8a, 0xb1, 0xbb, 0x72, - 0xa1, 0xc7, 0xb0, 0xdb, 0x5d, 0xc5, 0xb0, 0x5d, 0x9a, 0x8f, 0x24, 0x2b, 0xf8, 0xb8, 0x7e, 0xb9, - 0x5a, 0x99, 0x11, 0x52, 0x55, 0xa5, 0x30, 0x01, 0x0c, 0x9c, 0xf6, 0x65, 0xc1, 0x03, 0x7c, 0xdb, - 0x34, 0x9a, 0xb1, 0x07, 0xf8, 0xac, 0x0c, 0xf9, 0x01, 0x18, 0x2a, 0x7f, 0xd0, 0x75, 0x28, 0xf2, - 0xe5, 0x12, 0x7f, 0x2e, 0xe0, 0xeb, 0x23, 0xd2, 0x38, 0xf3, 0x58, 0x02, 0x8c, 0x22, 0xce, 0x3b, - 0x64, 0xa8, 0xfe, 0x64, 0x0e, 0x2e, 0x64, 0xb7, 0x8e, 0x7c, 0x12, 0x06, 0xd8, 0xce, 0xbc, 0xac, - 0x2d, 0x89, 0x4f, 0xe7, 0xa9, 0x81, 0xed, 0x16, 0x6d, 0xe8, 0x8e, 0x6c, 0xec, 0xfb, 0x64, 0xe4, - 0x6d, 0x18, 0xae, 0xba, 0x6e, 0x97, 0x3a, 0xf5, 0x97, 0xef, 0x68, 0x55, 0xb1, 0x27, 0xc4, 0x3d, - 0x87, 0x89, 0xe0, 0x86, 0xfb, 0x72, 0x2c, 0x3a, 0x97, 0x4c, 0xaf, 0xfe, 0x58, 0x0e, 0x2e, 0xf6, - 0xfa, 0x2a, 0xf2, 0x32, 0x0c, 0xae, 0x50, 0x4b, 0xb7, 0xbc, 0x6a, 0x45, 0x34, 0x09, 0xb7, 0x58, - 0x1e, 0xc2, 0xa2, 0x3b, 0x85, 0x80, 0x90, 0x15, 0xe2, 0xe7, 0x8a, 0x81, 0x23, 0x03, 0x3f, 0x03, - 0x45, 0x58, 0xac, 0x90, 0x4f, 0xa8, 0xfe, 0x5e, 0x1e, 0x46, 0x6a, 0xad, 0xee, 0x9a, 0x29, 0x2d, - 0x1c, 0xfb, 0xb6, 0xb7, 0x7d, 0xeb, 0x37, 0xbf, 0x37, 0xeb, 0x97, 0x0d, 0x37, 0x67, 0x9f, 0xc3, - 0xcd, 0x2f, 0x47, 0xde, 0x82, 0x52, 0x07, 0xbf, 0x23, 0x7e, 0x12, 0xcb, 0xbf, 0x2e, 0xeb, 0x24, - 0x96, 0x97, 0x61, 0xe3, 0xab, 0x79, 0x80, 0xf1, 0x15, 0x96, 0x95, 0x04, 0x1a, 0x2e, 0x12, 0x27, - 0x02, 0x3d, 0x14, 0x81, 0x86, 0x0b, 0xc2, 0x89, 0x40, 0x0f, 0x20, 0xd0, 0x5f, 0xcd, 0xc3, 0x58, - 0xb4, 0x4a, 0xf2, 0x49, 0x18, 0xe6, 0xd5, 0xf0, 0x73, 0xa1, 0x9c, 0xe4, 0x54, 0x1c, 0x82, 0x35, - 0xe0, 0x3f, 0xc4, 0x01, 0xd7, 0xf8, 0xba, 0xee, 0x36, 0xc2, 0x13, 0x1a, 0x7e, 0x7f, 0x3b, 0xc8, - 0x3d, 0xa1, 0x62, 0x28, 0x6d, 0x6c, 0x5d, 0x77, 0x67, 0xc2, 0xdf, 0x64, 0x16, 0x88, 0x43, 0xbb, - 0x2e, 0x8d, 0x32, 0x28, 0x22, 0x03, 0x91, 0x97, 0x3d, 0x8e, 0xd5, 0x4e, 0x71, 0x98, 0xcc, 0xe6, - 0x73, 0x41, 0xb3, 0x51, 0x19, 0xfa, 0x77, 0x91, 0x34, 0x5e, 0xa2, 0x4f, 0x3f, 0xe6, 0xe4, 0x04, - 0x15, 0xdd, 0xd3, 0xf9, 0xa6, 0xdc, 0xef, 0x00, 0xf5, 0x4f, 0x1c, 0xe8, 0x5f, 0xb6, 0xe8, 0xf2, - 0x7d, 0xf2, 0x12, 0x0c, 0x31, 0x85, 0x59, 0xb0, 0x59, 0x5f, 0xe6, 0x84, 0xff, 0x84, 0xa4, 0x49, - 0x88, 0x98, 0xef, 0xd3, 0x42, 0x2a, 0x72, 0x03, 0x20, 0x7c, 0x62, 0x26, 0xb4, 0x8f, 0xc8, 0x65, - 0x38, 0x66, 0xbe, 0x4f, 0x93, 0xe8, 0xfc, 0x52, 0xe2, 0x81, 0x4e, 0x21, 0x59, 0x8a, 0x63, 0xfc, - 0x52, 0x62, 0x7c, 0x2c, 0x00, 0x61, 0xbf, 0x6a, 0xba, 0xeb, 0x6e, 0xd8, 0x8e, 0x31, 0xb3, 0xae, - 0x5b, 0x6b, 0x34, 0xbe, 0x7b, 0x4a, 0x52, 0xcc, 0xf7, 0x69, 0x29, 0xe5, 0xc8, 0x1b, 0x30, 0x22, - 0x3b, 0x94, 0xc6, 0x9d, 0x3e, 0x64, 0xdc, 0x7c, 0x9f, 0x16, 0xa1, 0x25, 0xaf, 0xc1, 0xb0, 0xf8, - 0x7d, 0xcb, 0x16, 0x37, 0xca, 0x52, 0x2c, 0x22, 0x09, 0x35, 0xdf, 0xa7, 0xc9, 0x94, 0x52, 0xa5, - 0x35, 0xc7, 0xb4, 0x3c, 0xf1, 0x46, 0x39, 0x5e, 0x29, 0xe2, 0xa4, 0x4a, 0xf1, 0x37, 0x79, 0x1b, - 0x46, 0x83, 0x20, 0x4f, 0xef, 0xd3, 0xa6, 0x27, 0x0e, 0xbf, 0xcf, 0xc6, 0x0a, 0x73, 0xe4, 0x7c, - 0x9f, 0x16, 0xa5, 0x26, 0x57, 0xa0, 0xa4, 0x51, 0xd7, 0xfc, 0xc0, 0xbf, 0x2e, 0x1e, 0x93, 0xc6, - 0xb9, 0xf9, 0x01, 0x93, 0x92, 0xc0, 0xb3, 0xde, 0x09, 0xef, 0xa7, 0xc5, 0x51, 0x35, 0x89, 0xd5, - 0x32, 0x6b, 0x19, 0xac, 0x77, 0x24, 0xe7, 0x84, 0x4f, 0x87, 0xa1, 0xaf, 0x44, 0xe6, 0xd7, 0xe1, - 0x78, 0x8c, 0x01, 0x19, 0x3b, 0xdf, 0xa7, 0xc5, 0xe8, 0x25, 0xa9, 0x56, 0x4c, 0xf7, 0x81, 0x08, - 0x59, 0x1a, 0x97, 0x2a, 0x43, 0x49, 0x52, 0x65, 0x3f, 0xa5, 0xaa, 0x97, 0xa8, 0xb7, 0x61, 0x3b, - 0x0f, 0x44, 0x80, 0xd2, 0x78, 0xd5, 0x02, 0x2b, 0x55, 0x2d, 0x20, 0x72, 0xd5, 0x6c, 0xc0, 0x8d, - 0xa5, 0x57, 0xad, 0x7b, 0xba, 0x5c, 0x35, 0x3f, 0x89, 0xf3, 0x3b, 0x69, 0x81, 0xea, 0x0f, 0xa9, - 0x32, 0x9e, 0xda, 0xa1, 0x88, 0x93, 0x3a, 0x14, 0x7f, 0xb3, 0x4a, 0xa5, 0xb4, 0xf0, 0xca, 0x44, - 0xb4, 0x52, 0x09, 0xc5, 0x2a, 0x95, 0x13, 0xc8, 0xdf, 0x90, 0x73, 0x8f, 0x2b, 0xa7, 0xa2, 0x1d, - 0x14, 0x62, 0x58, 0x07, 0x49, 0x39, 0xca, 0x27, 0x31, 0xaf, 0xb1, 0x42, 0x90, 0x7c, 0x38, 0x68, - 0xe1, 0x4c, 0x6d, 0xbe, 0x4f, 0xc3, 0x8c, 0xc7, 0x2a, 0xcf, 0x98, 0xad, 0x9c, 0x46, 0x8a, 0x11, - 0x9f, 0x82, 0xc1, 0xe6, 0xfb, 0x34, 0x9e, 0x4d, 0xfb, 0x25, 0x29, 0xab, 0xa0, 0x72, 0x26, 0x3a, - 0x45, 0x04, 0x08, 0x36, 0x45, 0x84, 0xb9, 0x07, 0xe7, 0x92, 0xb9, 0xf3, 0x94, 0xb3, 0xd1, 0xa5, - 0x26, 0x8e, 0x9f, 0xef, 0xd3, 0x92, 0xf9, 0xf6, 0x5e, 0x8b, 0xa4, 0x93, 0x53, 0xce, 0xc5, 0x02, - 0x80, 0x85, 0x28, 0x26, 0x2e, 0x39, 0xf1, 0xdc, 0x32, 0x9c, 0xe6, 0xd9, 0x68, 0x45, 0x08, 0x2f, - 0x31, 0x59, 0x9d, 0x8f, 0x6e, 0x5c, 0x52, 0x48, 0xe6, 0xfb, 0xb4, 0xb4, 0x92, 0x64, 0x26, 0x91, - 0xd4, 0x4d, 0x51, 0xa2, 0xbe, 0x31, 0x31, 0xf4, 0x7c, 0x9f, 0x96, 0x48, 0x03, 0x77, 0x43, 0xce, - 0xa6, 0xa6, 0x3c, 0x11, 0xed, 0xc4, 0x10, 0xc3, 0x3a, 0x51, 0xca, 0xba, 0x76, 0x43, 0xce, 0xb0, - 0xa5, 0x5c, 0x48, 0x96, 0x0a, 0x67, 0x4e, 0x29, 0x13, 0x97, 0x96, 0x9e, 0x34, 0x48, 0x79, 0x52, - 0xa4, 0x0e, 0x16, 0xe5, 0xd3, 0x68, 0xe6, 0xfb, 0xb4, 0xf4, 0x84, 0x43, 0x5a, 0x7a, 0xb6, 0x1d, - 0xe5, 0x62, 0x2f, 0x9e, 0x41, 0xeb, 0xd2, 0x33, 0xf5, 0xe8, 0x3d, 0x72, 0x9f, 0x28, 0x97, 0xa2, - 0x21, 0x8c, 0x33, 0x09, 0xe7, 0xfb, 0xb4, 0x1e, 0x19, 0x54, 0xee, 0x64, 0x24, 0x22, 0x51, 0x9e, - 0x8a, 0x66, 0x0e, 0x4f, 0x25, 0x9a, 0xef, 0xd3, 0x32, 0xd2, 0x98, 0xdc, 0xc9, 0xc8, 0x53, 0xa1, - 0x4c, 0xf6, 0x64, 0x1b, 0xc8, 0x23, 0x23, 0xcb, 0xc5, 0x72, 0x6a, 0x8a, 0x07, 0xe5, 0xe9, 0xa8, - 0xea, 0xa6, 0x90, 0x30, 0xd5, 0x4d, 0x4b, 0x0e, 0xb1, 0x9c, 0x9a, 0x93, 0x40, 0x79, 0xa6, 0x07, - 0xc3, 0xa0, 0x8d, 0xa9, 0xd9, 0x0c, 0x96, 0x53, 0x93, 0x02, 0x28, 0x6a, 0x94, 0x61, 0x0a, 0x09, - 0x63, 0x98, 0x96, 0x4e, 0x60, 0x39, 0x35, 0x76, 0xbc, 0xf2, 0x6c, 0x0f, 0x86, 0x61, 0x0b, 0xd3, - 0xa2, 0xce, 0xbf, 0x16, 0x09, 0xde, 0xae, 0x3c, 0x17, 0x9d, 0x37, 0x24, 0x14, 0x9b, 0x37, 0xe4, - 0x30, 0xef, 0x33, 0x89, 0xc8, 0xb2, 0xca, 0xc7, 0xa2, 0xc3, 0x3c, 0x86, 0x66, 0xc3, 0x3c, 0x1e, - 0x8b, 0x76, 0x26, 0x11, 0x61, 0x53, 0xb9, 0x9c, 0xc5, 0x04, 0xd1, 0x51, 0x26, 0x3c, 0x26, 0x67, - 0x35, 0x25, 0xc4, 0xa3, 0xf2, 0xf1, 0xa8, 0x5f, 0x77, 0x82, 0x60, 0xbe, 0x4f, 0x4b, 0x09, 0x0c, - 0xa9, 0xa5, 0xc7, 0x33, 0x52, 0xae, 0x44, 0x87, 0x6d, 0x1a, 0x0d, 0x1b, 0xb6, 0xa9, 0xb1, 0x90, - 0x16, 0xd2, 0x1e, 0x9f, 0x28, 0x57, 0xa3, 0x86, 0x59, 0x92, 0x82, 0x19, 0x66, 0x29, 0x8f, 0x56, - 0xb4, 0xf4, 0x18, 0x3b, 0xca, 0xf3, 0x3d, 0x5b, 0x88, 0x34, 0x29, 0x2d, 0xe4, 0x21, 0x67, 0x42, - 0xdb, 0xe9, 0x4e, 0xa7, 0x65, 0xeb, 0x86, 0xf2, 0x89, 0x54, 0xdb, 0x89, 0x23, 0x25, 0xdb, 0x89, - 0x03, 0xd8, 0x2a, 0x2f, 0xbf, 0x71, 0x50, 0x5e, 0x88, 0xae, 0xf2, 0x32, 0x8e, 0xad, 0xf2, 0x91, - 0xf7, 0x10, 0x33, 0x89, 0xf7, 0x00, 0xca, 0x8b, 0x51, 0x05, 0x88, 0xa1, 0x99, 0x02, 0xc4, 0x5f, - 0x10, 0x7c, 0x3e, 0xdb, 0x83, 0x5e, 0x99, 0x42, 0x6e, 0x4f, 0xfb, 0xdc, 0xb2, 0xe8, 0xe6, 0xfb, - 0xb4, 0x6c, 0x2f, 0xfc, 0x6a, 0x8a, 0x43, 0xbc, 0x72, 0x2d, 0xaa, 0x60, 0x09, 0x02, 0xa6, 0x60, - 0x49, 0x37, 0xfa, 0x6a, 0x8a, 0x47, 0xbb, 0xf2, 0xc9, 0x4c, 0x56, 0xc1, 0x37, 0xa7, 0xf8, 0xc1, - 0xdf, 0x90, 0x5d, 0xd2, 0x95, 0x97, 0xa2, 0x8b, 0x5d, 0x88, 0x61, 0x8b, 0x9d, 0xe4, 0xba, 0x7e, - 0x43, 0x76, 0xc6, 0x56, 0xae, 0x27, 0x4b, 0x85, 0x4b, 0xa4, 0xe4, 0xb4, 0xad, 0xa5, 0xfb, 0x30, - 0x2b, 0x2f, 0x47, 0xb5, 0x2e, 0x8d, 0x86, 0x69, 0x5d, 0xaa, 0xff, 0xf3, 0x5c, 0xd2, 0x15, 0x59, - 0xb9, 0x11, 0xdf, 0x64, 0x47, 0xf1, 0xcc, 0xf2, 0x49, 0xb8, 0x2f, 0x7f, 0x3a, 0x1e, 0x6c, 0x4f, - 0x79, 0x25, 0x76, 0xed, 0x1b, 0xc1, 0x32, 0xfb, 0x36, 0x16, 0x9c, 0xef, 0xd3, 0xf1, 0xf8, 0x74, - 0xca, 0xab, 0xe9, 0x1c, 0x02, 0x5d, 0x89, 0xc7, 0xb3, 0xfb, 0x74, 0x3c, 0xa4, 0x9b, 0xf2, 0x5a, - 0x3a, 0x87, 0x40, 0xba, 0xf1, 0x10, 0x70, 0x2f, 0x49, 0x41, 0xe6, 0x95, 0x4f, 0x45, 0x4d, 0xc7, - 0x00, 0xc1, 0x4c, 0xc7, 0x30, 0x14, 0xfd, 0x4b, 0x52, 0x70, 0x76, 0xe5, 0xf5, 0x44, 0x91, 0xa0, - 0xb1, 0x52, 0x08, 0xf7, 0x97, 0xa4, 0xa0, 0xe6, 0xca, 0x1b, 0x89, 0x22, 0x41, 0xeb, 0xa4, 0xd0, - 0xe7, 0x46, 0xaf, 0xf7, 0xab, 0xca, 0x9b, 0xd1, 0xc3, 0xe0, 0x6c, 0xca, 0xf9, 0x3e, 0xad, 0xd7, - 0x3b, 0xd8, 0xcf, 0x67, 0x3b, 0x76, 0x2b, 0x6f, 0x45, 0x87, 0x70, 0x16, 0x1d, 0x1b, 0xc2, 0x99, - 0xce, 0xe1, 0x6f, 0xc7, 0x62, 0x59, 0x28, 0x6f, 0x47, 0xa7, 0xb8, 0x08, 0x92, 0x4d, 0x71, 0xf1, - 0xc8, 0x17, 0x91, 0x20, 0x0d, 0xca, 0xf7, 0x45, 0xa7, 0x38, 0x19, 0xc7, 0xa6, 0xb8, 0x48, 0x40, - 0x87, 0x99, 0x44, 0xec, 0x00, 0xe5, 0x9d, 0xe8, 0x14, 0x17, 0x43, 0xb3, 0x29, 0x2e, 0x1e, 0x6d, - 0xe0, 0xed, 0xd8, 0x13, 0x7a, 0xe5, 0xd3, 0xe9, 0xed, 0x47, 0xa4, 0xdc, 0x7e, 0xfe, 0xe0, 0x5e, - 0x4b, 0x7f, 0x0b, 0xae, 0x94, 0xa3, 0xe3, 0x37, 0x8d, 0x86, 0x8d, 0xdf, 0xd4, 0x77, 0xe4, 0xf1, - 0x8d, 0x83, 0xd0, 0xaa, 0xe9, 0x1e, 0x1b, 0x87, 0xd0, 0x14, 0x49, 0x01, 0x47, 0xf6, 0xc8, 0x7c, - 0x23, 0x34, 0x93, 0xb1, 0x47, 0xf6, 0xb7, 0x41, 0x31, 0x7a, 0x36, 0xbb, 0x26, 0xfc, 0x8c, 0x95, - 0x4a, 0x74, 0x76, 0x4d, 0x10, 0xb0, 0xd9, 0x35, 0xe9, 0x9d, 0x3c, 0x07, 0x13, 0x42, 0x8b, 0xb8, - 0xfb, 0xb4, 0x69, 0xad, 0x29, 0xb3, 0xb1, 0xf7, 0x96, 0x31, 0x3c, 0x9b, 0x9d, 0xe2, 0x30, 0x5c, - 0xaf, 0x39, 0x6c, 0xa6, 0x65, 0x76, 0x56, 0x6d, 0xdd, 0x31, 0xea, 0xd4, 0x32, 0x94, 0xb9, 0xd8, - 0x7a, 0x9d, 0x42, 0x83, 0xeb, 0x75, 0x0a, 0x1c, 0x43, 0xc4, 0xc5, 0xe0, 0x1a, 0x6d, 0x52, 0xf3, - 0x21, 0x55, 0x6e, 0x22, 0xdb, 0xc9, 0x2c, 0xb6, 0x82, 0x6c, 0xbe, 0x4f, 0xcb, 0xe2, 0xc0, 0x6c, - 0xf5, 0xc5, 0xcd, 0xfa, 0xbb, 0x0b, 0x41, 0xf8, 0x81, 0x9a, 0x43, 0x3b, 0xba, 0x43, 0x95, 0xf9, - 0xa8, 0xad, 0x9e, 0x4a, 0xc4, 0x6c, 0xf5, 0x54, 0x44, 0x92, 0xad, 0x3f, 0x16, 0xaa, 0xbd, 0xd8, - 0x86, 0x23, 0x22, 0xbd, 0x34, 0x9b, 0x9d, 0xa2, 0x08, 0x26, 0xa0, 0x05, 0xdb, 0x5a, 0xc3, 0x93, - 0x8a, 0x5b, 0xd1, 0xd9, 0x29, 0x9b, 0x92, 0xcd, 0x4e, 0xd9, 0x58, 0xa6, 0xea, 0x51, 0x2c, 0x1f, - 0x83, 0xb7, 0xa3, 0xaa, 0x9e, 0x42, 0xc2, 0x54, 0x3d, 0x05, 0x9c, 0x64, 0xa8, 0x51, 0x97, 0x7a, - 0xca, 0x42, 0x2f, 0x86, 0x48, 0x92, 0x64, 0x88, 0xe0, 0x24, 0xc3, 0x39, 0xea, 0x35, 0xd7, 0x95, - 0xc5, 0x5e, 0x0c, 0x91, 0x24, 0xc9, 0x10, 0xc1, 0x6c, 0xb3, 0x19, 0x05, 0x4f, 0x77, 0x5b, 0x0f, - 0xfc, 0x3e, 0x5b, 0x8a, 0x6e, 0x36, 0x33, 0x09, 0xd9, 0x66, 0x33, 0x13, 0x49, 0x7e, 0x6c, 0xd7, - 0x7e, 0xf0, 0xca, 0x32, 0x56, 0x38, 0x15, 0xda, 0x05, 0xbb, 0x29, 0x35, 0xdf, 0xa7, 0xed, 0xd6, - 0xcf, 0xfe, 0x13, 0x81, 0xd3, 0xa8, 0x52, 0xc3, 0xaa, 0xc6, 0x83, 0xb3, 0x0a, 0x0e, 0x9e, 0xef, - 0xd3, 0x02, 0xb7, 0xd2, 0xd7, 0x60, 0x18, 0x3f, 0xaa, 0x6a, 0x99, 0x5e, 0x65, 0x5a, 0x79, 0x37, - 0xba, 0x65, 0x92, 0x50, 0x6c, 0xcb, 0x24, 0xfd, 0x64, 0x93, 0x38, 0xfe, 0xe4, 0x53, 0x4c, 0x65, - 0x5a, 0xd1, 0xa2, 0x93, 0x78, 0x04, 0xc9, 0x26, 0xf1, 0x08, 0x20, 0xa8, 0xb7, 0xe2, 0xd8, 0x9d, - 0xca, 0xb4, 0x52, 0x4f, 0xa9, 0x97, 0xa3, 0x82, 0x7a, 0xf9, 0xcf, 0xa0, 0xde, 0xfa, 0x7a, 0xd7, - 0xab, 0xb0, 0x6f, 0x5c, 0x49, 0xa9, 0xd7, 0x47, 0x06, 0xf5, 0xfa, 0x00, 0x36, 0x15, 0x22, 0xa0, - 0xe6, 0xd8, 0x6c, 0xd2, 0xbe, 0x6d, 0xb6, 0x5a, 0xca, 0x9d, 0xe8, 0x54, 0x18, 0xc7, 0xb3, 0xa9, - 0x30, 0x0e, 0x63, 0xa6, 0x27, 0x6f, 0x15, 0x5d, 0xed, 0xae, 0x29, 0x77, 0xa3, 0xa6, 0x67, 0x88, - 0x61, 0xa6, 0x67, 0xf8, 0x0b, 0x77, 0x17, 0xec, 0x97, 0x46, 0xef, 0x3b, 0xd4, 0x5d, 0x57, 0xee, - 0xc5, 0x76, 0x17, 0x12, 0x0e, 0x77, 0x17, 0xd2, 0x6f, 0xb2, 0x06, 0x4f, 0x46, 0x16, 0x1a, 0xff, - 0xd2, 0xa6, 0x4e, 0x75, 0xa7, 0xb9, 0xae, 0x7c, 0x06, 0x59, 0x3d, 0x9b, 0xba, 0x54, 0x45, 0x49, - 0xe7, 0xfb, 0xb4, 0x5e, 0x9c, 0x70, 0x5b, 0xfe, 0xee, 0x02, 0x8f, 0x04, 0xab, 0xd5, 0x66, 0xfc, - 0x4d, 0xe8, 0x7b, 0xb1, 0x6d, 0x79, 0x92, 0x04, 0xb7, 0xe5, 0x49, 0x30, 0xe9, 0xc0, 0x53, 0xb1, - 0xad, 0xda, 0xa2, 0xde, 0x62, 0xfb, 0x12, 0x6a, 0xd4, 0xf4, 0xe6, 0x03, 0xea, 0x29, 0x9f, 0x45, - 0xde, 0x97, 0x33, 0x36, 0x7c, 0x31, 0xea, 0xf9, 0x3e, 0x6d, 0x07, 0x7e, 0x44, 0x85, 0x62, 0x7d, - 0x6e, 0xa5, 0xa6, 0x7c, 0x7f, 0xf4, 0x7c, 0x93, 0xc1, 0xe6, 0xfb, 0x34, 0xc4, 0x31, 0x2b, 0xed, - 0x4e, 0x67, 0xcd, 0xd1, 0x0d, 0xca, 0x0d, 0x2d, 0xb4, 0xdd, 0x84, 0x01, 0xfa, 0x03, 0x51, 0x2b, - 0x2d, 0x8b, 0x8e, 0x59, 0x69, 0x59, 0x38, 0xa6, 0xa8, 0x91, 0xa4, 0x27, 0xca, 0xe7, 0xa2, 0x8a, - 0x1a, 0x41, 0x32, 0x45, 0x8d, 0xa6, 0x48, 0xf9, 0x0c, 0x9c, 0x0b, 0xf6, 0xf3, 0x62, 0xfd, 0xe5, - 0x9d, 0xa6, 0x7c, 0x1e, 0xf9, 0x3c, 0x95, 0xb8, 0x0c, 0x88, 0x50, 0xcd, 0xf7, 0x69, 0x19, 0xe5, - 0xd9, 0x8a, 0x9b, 0x48, 0x0a, 0x26, 0xcc, 0x8b, 0x1f, 0x8c, 0xae, 0xb8, 0x19, 0x64, 0x6c, 0xc5, - 0xcd, 0x40, 0xa5, 0x32, 0x17, 0x42, 0xd5, 0x77, 0x60, 0x1e, 0xc8, 0x34, 0x8b, 0x43, 0x2a, 0x73, - 0x61, 0xa9, 0xad, 0xee, 0xc0, 0x3c, 0xb0, 0xd6, 0xb2, 0x38, 0x90, 0x2b, 0x50, 0xaa, 0xd7, 0x17, - 0xb5, 0xae, 0xa5, 0x34, 0x63, 0xde, 0xb2, 0x08, 0x9d, 0xef, 0xd3, 0x04, 0x9e, 0x99, 0x41, 0xb3, - 0x2d, 0xdd, 0xf5, 0xcc, 0xa6, 0x8b, 0x23, 0xc6, 0x1f, 0x21, 0x46, 0xd4, 0x0c, 0x4a, 0xa3, 0x61, - 0x66, 0x50, 0x1a, 0x9c, 0xd9, 0x8b, 0x33, 0xba, 0xeb, 0xea, 0x96, 0xe1, 0xe8, 0xd3, 0xb8, 0x4c, - 0xd0, 0xd8, 0x6b, 0xac, 0x08, 0x96, 0xd9, 0x8b, 0x51, 0x08, 0x1e, 0xbe, 0xfb, 0x10, 0xdf, 0xcc, - 0xb9, 0x1f, 0x3b, 0x7c, 0x8f, 0xe1, 0xf1, 0xf0, 0x3d, 0x06, 0x43, 0xbb, 0xd3, 0x87, 0x69, 0x74, - 0xcd, 0x64, 0x22, 0x52, 0xd6, 0x62, 0x76, 0x67, 0x9c, 0x00, 0xed, 0xce, 0x38, 0x30, 0xd2, 0x24, - 0x7f, 0xb9, 0x5d, 0xcf, 0x68, 0x52, 0xb8, 0xca, 0x26, 0xca, 0xb0, 0xf5, 0x3b, 0x1c, 0x1c, 0x95, - 0x4d, 0x4b, 0x6f, 0xdb, 0x95, 0x69, 0x5f, 0xea, 0x66, 0x74, 0xfd, 0xce, 0x24, 0x64, 0xeb, 0x77, - 0x26, 0x92, 0xcd, 0xae, 0xfe, 0x46, 0x6b, 0x5d, 0x77, 0xa8, 0x51, 0x31, 0x1d, 0x3c, 0x59, 0xdc, - 0xe4, 0x5b, 0xc3, 0xf7, 0xa3, 0xb3, 0x6b, 0x0f, 0x52, 0x36, 0xbb, 0xf6, 0x40, 0x33, 0x23, 0x2f, - 0x1d, 0xad, 0x51, 0xdd, 0x50, 0x1e, 0x44, 0x8d, 0xbc, 0x6c, 0x4a, 0x66, 0xe4, 0x65, 0x63, 0xb3, - 0x3f, 0xe7, 0x9e, 0x63, 0x7a, 0x54, 0x69, 0xed, 0xe6, 0x73, 0x90, 0x34, 0xfb, 0x73, 0x10, 0xcd, - 0x36, 0x84, 0xf1, 0x0e, 0x69, 0x47, 0x37, 0x84, 0xc9, 0x6e, 0x88, 0x97, 0x60, 0x16, 0x8b, 0x78, - 0x94, 0xa7, 0x58, 0x51, 0x8b, 0x45, 0x80, 0x99, 0xc5, 0x12, 0x3e, 0xdb, 0x8b, 0x3c, 0xc5, 0x52, - 0xec, 0xe8, 0x1a, 0x2a, 0xe3, 0xd8, 0x1a, 0x1a, 0x79, 0xb6, 0xf5, 0x5a, 0xe4, 0x9d, 0x81, 0xd2, - 0x89, 0x5a, 0x1d, 0x12, 0x8a, 0x59, 0x1d, 0xf2, 0x8b, 0x84, 0x19, 0x18, 0xc7, 0x5b, 0x70, 0xad, - 0x1b, 0xdc, 0xe3, 0xfc, 0x50, 0xf4, 0x33, 0x63, 0x68, 0xf6, 0x99, 0x31, 0x50, 0x84, 0x89, 0x98, - 0xb6, 0x9c, 0x0c, 0x26, 0xe1, 0xf9, 0x60, 0x0c, 0x44, 0x16, 0x80, 0xd4, 0xcb, 0x8b, 0x0b, 0x55, - 0xa3, 0x26, 0x5f, 0x91, 0xb9, 0xd1, 0x13, 0xd8, 0x24, 0xc5, 0x7c, 0x9f, 0x96, 0x52, 0x8e, 0xbc, - 0x0f, 0x17, 0x05, 0x54, 0xbc, 0xb8, 0xae, 0x39, 0xf6, 0x43, 0xd3, 0x08, 0x16, 0x04, 0x2f, 0xea, - 0xc7, 0xd6, 0x8b, 0x76, 0xbe, 0x4f, 0xeb, 0xc9, 0x2b, 0xbb, 0x2e, 0xb1, 0x3e, 0x74, 0x77, 0x53, - 0x57, 0xb0, 0x48, 0xf4, 0xe4, 0x95, 0x5d, 0x97, 0x90, 0xfb, 0xc3, 0xdd, 0xd4, 0x15, 0x74, 0x42, - 0x4f, 0x5e, 0xc4, 0x85, 0xc9, 0x5e, 0xf8, 0x72, 0xab, 0xa5, 0x6c, 0x60, 0x75, 0x1f, 0xdf, 0x4d, - 0x75, 0x65, 0x34, 0x38, 0x77, 0xe2, 0xc8, 0x66, 0xe9, 0xe5, 0x0e, 0xb5, 0xea, 0x91, 0x05, 0xe8, - 0x51, 0x74, 0x96, 0x4e, 0x10, 0xb0, 0x59, 0x3a, 0x01, 0x64, 0x03, 0x4a, 0x7e, 0xae, 0xa2, 0x6c, - 0x46, 0x07, 0x94, 0x8c, 0x63, 0x03, 0x2a, 0xf2, 0xb4, 0x65, 0x19, 0x4e, 0x2f, 0x3f, 0xf0, 0x74, - 0xdf, 0x82, 0x74, 0x45, 0x57, 0x7e, 0x10, 0xbb, 0x64, 0x4a, 0x92, 0xe0, 0x25, 0x53, 0x12, 0xcc, - 0xc6, 0x08, 0x03, 0xd7, 0x37, 0xad, 0xe6, 0x9c, 0x6e, 0xb6, 0xba, 0x0e, 0x55, 0xfe, 0x54, 0x74, - 0x8c, 0xc4, 0xd0, 0x6c, 0x8c, 0xc4, 0x40, 0x6c, 0x81, 0x66, 0xa0, 0xb2, 0xeb, 0x9a, 0x6b, 0x96, - 0xd8, 0x57, 0x76, 0x5b, 0x9e, 0xf2, 0x6f, 0x44, 0x17, 0xe8, 0x34, 0x1a, 0xb6, 0x40, 0xa7, 0xc1, - 0xf1, 0xd4, 0x89, 0xf5, 0x02, 0x5b, 0x3c, 0xe4, 0xbb, 0xca, 0x7f, 0x33, 0x76, 0xea, 0x94, 0x42, - 0x83, 0xa7, 0x4e, 0x29, 0x70, 0xb6, 0x3e, 0x72, 0x9b, 0x6c, 0xc1, 0x0c, 0xee, 0xaa, 0xff, 0xad, - 0xe8, 0xfa, 0x18, 0xc7, 0xb3, 0xf5, 0x31, 0x0e, 0x8b, 0xf2, 0x11, 0x5d, 0xf0, 0x6f, 0x67, 0xf1, - 0x09, 0xe4, 0x9f, 0x28, 0x43, 0x6e, 0xca, 0x7c, 0xc4, 0x48, 0xf9, 0xe1, 0x5c, 0x16, 0xa3, 0x60, - 0x78, 0x24, 0x0a, 0x45, 0x19, 0x69, 0xf4, 0xa1, 0x49, 0x37, 0x94, 0x2f, 0x64, 0x32, 0xe2, 0x04, - 0x51, 0x46, 0x1c, 0x46, 0xde, 0x83, 0x73, 0x21, 0x6c, 0x91, 0xb6, 0x57, 0x83, 0x99, 0xe9, 0x4f, - 0xe7, 0xa2, 0x66, 0x70, 0x3a, 0x19, 0x33, 0x83, 0xd3, 0x31, 0x69, 0xac, 0x85, 0xe8, 0xfe, 0x9d, - 0x1d, 0x58, 0x07, 0x12, 0xcc, 0x60, 0x90, 0xc6, 0x5a, 0x48, 0xf3, 0x47, 0x76, 0x60, 0x1d, 0xc8, - 0x34, 0x83, 0x01, 0xf9, 0xf1, 0x1c, 0x5c, 0x4e, 0x47, 0x95, 0x5b, 0xad, 0x39, 0xdb, 0x09, 0x71, - 0xca, 0x9f, 0xc9, 0x45, 0x0f, 0x1a, 0x76, 0x57, 0x6c, 0xbe, 0x4f, 0xdb, 0x65, 0x05, 0xe4, 0xfb, - 0x60, 0xb4, 0xdc, 0x35, 0x4c, 0x0f, 0x2f, 0xde, 0x98, 0xe1, 0xfc, 0xa3, 0xb9, 0xd8, 0x16, 0x47, - 0xc6, 0xe2, 0x16, 0x47, 0x06, 0x90, 0x5b, 0x70, 0xaa, 0x4e, 0x9b, 0x5d, 0xc7, 0xf4, 0x36, 0x35, - 0xda, 0xb1, 0x1d, 0x8f, 0xf1, 0xf8, 0xb3, 0xb9, 0xe8, 0x24, 0x96, 0xa0, 0x60, 0x93, 0x58, 0x02, - 0x48, 0xee, 0x26, 0x6e, 0xe5, 0x45, 0x67, 0xfe, 0x58, 0xae, 0xe7, 0xb5, 0x7c, 0xd0, 0x97, 0xe9, - 0xc5, 0x49, 0x2d, 0x76, 0x8b, 0x2e, 0xb8, 0xfe, 0x78, 0xae, 0xc7, 0x35, 0xba, 0x34, 0xc3, 0x25, - 0xc1, 0x8c, 0x63, 0x4a, 0x1a, 0x79, 0xe5, 0xcf, 0xe5, 0x7a, 0x5c, 0x7b, 0x87, 0x1c, 0xd3, 0x32, - 0xd0, 0xbf, 0xc2, 0x3d, 0x45, 0x04, 0xa3, 0x9f, 0xc8, 0x25, 0x5d, 0x45, 0x82, 0xf2, 0x12, 0x21, - 0x2b, 0x76, 0xc7, 0x0d, 0x94, 0xfe, 0x8b, 0xb9, 0xa4, 0x6f, 0x5e, 0x58, 0x2c, 0xfc, 0x45, 0x28, - 0x5c, 0x98, 0x7d, 0xe4, 0x51, 0xc7, 0xd2, 0x5b, 0xd8, 0x9d, 0x75, 0xcf, 0x76, 0xf4, 0x35, 0x3a, - 0x6b, 0xe9, 0xab, 0x2d, 0xaa, 0xfc, 0x64, 0x2e, 0x6a, 0xc1, 0x66, 0x93, 0x32, 0x0b, 0x36, 0x1b, - 0x4b, 0xd6, 0xe1, 0xc9, 0x34, 0x6c, 0xc5, 0x74, 0xb1, 0x9e, 0x2f, 0xe5, 0xa2, 0x26, 0x6c, 0x0f, - 0x5a, 0x66, 0xc2, 0xf6, 0x40, 0x93, 0xeb, 0x30, 0x34, 0x6d, 0xfb, 0xd3, 0xef, 0x9f, 0x8f, 0x39, - 0x43, 0x06, 0x98, 0xf9, 0x3e, 0x2d, 0x24, 0x13, 0x65, 0xc4, 0xa0, 0xfe, 0x72, 0xb2, 0x4c, 0x78, - 0xf9, 0x14, 0xfc, 0x10, 0x65, 0x84, 0xb8, 0xff, 0xdd, 0x64, 0x99, 0xf0, 0x8e, 0x2b, 0xf8, 0xc1, - 0x66, 0x12, 0x5e, 0xe3, 0xe2, 0x5c, 0x99, 0xd9, 0x6d, 0x33, 0xeb, 0x7a, 0xab, 0x45, 0xad, 0x35, - 0xaa, 0x7c, 0x25, 0x36, 0x93, 0xa4, 0x93, 0xb1, 0x99, 0x24, 0x1d, 0x43, 0x7e, 0x00, 0xce, 0xdf, - 0xd5, 0x5b, 0xa6, 0x11, 0xe2, 0xfc, 0xa4, 0xe2, 0xca, 0x4f, 0xe5, 0xa2, 0xbb, 0xe9, 0x0c, 0x3a, - 0xb6, 0x9b, 0xce, 0x40, 0x91, 0x45, 0x20, 0xb8, 0x8c, 0x06, 0xb3, 0x05, 0x5b, 0x9f, 0x95, 0x7f, - 0x2f, 0x17, 0xb5, 0x53, 0x93, 0x24, 0xcc, 0x4e, 0x4d, 0x42, 0x49, 0x23, 0x3b, 0x35, 0x88, 0xf2, - 0xd3, 0xb9, 0xe8, 0x69, 0x4d, 0x16, 0xe1, 0x7c, 0x9f, 0x96, 0x9d, 0x5f, 0xe4, 0x26, 0x4c, 0xd4, - 0x6b, 0xd5, 0xb9, 0xb9, 0xd9, 0xfa, 0xdd, 0x6a, 0x05, 0x1f, 0x3a, 0x18, 0xca, 0xcf, 0xc4, 0x56, - 0xac, 0x38, 0x01, 0x5b, 0xb1, 0xe2, 0x30, 0xf2, 0x26, 0x8c, 0xb0, 0xf6, 0xb3, 0x01, 0x83, 0x9f, - 0xfc, 0xd5, 0x5c, 0xd4, 0x9c, 0x92, 0x91, 0xcc, 0x9c, 0x92, 0x7f, 0x93, 0x3a, 0x9c, 0x61, 0x52, - 0xac, 0x39, 0xf4, 0x3e, 0x75, 0xa8, 0xd5, 0xf4, 0xc7, 0xf4, 0xcf, 0xe6, 0xa2, 0x56, 0x46, 0x1a, - 0x11, 0xb3, 0x32, 0xd2, 0xe0, 0xe4, 0x01, 0x5c, 0x8c, 0x9f, 0x04, 0xc9, 0xcf, 0x4e, 0x95, 0xbf, - 0x90, 0x8b, 0x19, 0xc3, 0x3d, 0x88, 0xd1, 0x18, 0xee, 0x81, 0x27, 0x16, 0x5c, 0x12, 0xc7, 0x2a, - 0xc2, 0xe1, 0x32, 0x5e, 0xdb, 0xcf, 0xf1, 0xda, 0x3e, 0x16, 0x3a, 0x04, 0xf6, 0xa0, 0x9e, 0xef, - 0xd3, 0x7a, 0xb3, 0x63, 0x7a, 0x96, 0x4c, 0x80, 0xa1, 0xfc, 0xc5, 0x5c, 0xba, 0x47, 0x4a, 0xc4, - 0x4d, 0x39, 0x2d, 0x73, 0xc6, 0x7b, 0x59, 0xe9, 0x1b, 0x94, 0xbf, 0x14, 0x1b, 0x6f, 0xe9, 0x64, - 0x6c, 0xbc, 0x65, 0xe4, 0x7f, 0xb8, 0x05, 0xa7, 0xb8, 0x52, 0xd7, 0x74, 0x1c, 0x86, 0xd6, 0x1a, - 0x35, 0x94, 0x7f, 0x3f, 0xb6, 0xda, 0x25, 0x28, 0xd0, 0xb5, 0x27, 0x0e, 0x64, 0x53, 0x77, 0xbd, - 0xa3, 0x5b, 0x16, 0x1e, 0xb3, 0x2a, 0xff, 0x41, 0x6c, 0xea, 0x0e, 0x51, 0xe8, 0xb8, 0x1b, 0xfc, - 0x62, 0x9a, 0xd0, 0x2b, 0xf5, 0x91, 0xf2, 0x1f, 0xc6, 0x34, 0xa1, 0x17, 0x31, 0xd3, 0x84, 0x9e, - 0x79, 0x94, 0xee, 0x66, 0x3c, 0x01, 0x57, 0xbe, 0x16, 0x5b, 0x91, 0x53, 0xa9, 0xd8, 0x8a, 0x9c, - 0xfe, 0x82, 0xfc, 0x6e, 0xc6, 0xf3, 0x69, 0xe5, 0xe7, 0x7b, 0xf3, 0x0d, 0x57, 0xfa, 0xf4, 0xd7, - 0xd7, 0x77, 0x33, 0x9e, 0x1e, 0x2b, 0x7f, 0xb9, 0x37, 0xdf, 0xd0, 0xb1, 0x2f, 0xfd, 0xe5, 0x72, - 0x23, 0xfb, 0xd9, 0xae, 0xf2, 0x57, 0xe2, 0x53, 0x57, 0x06, 0x21, 0x4e, 0x5d, 0x59, 0x6f, 0x7f, - 0x57, 0xe1, 0x09, 0xae, 0x21, 0x37, 0x1d, 0xbd, 0xb3, 0x5e, 0xa7, 0x9e, 0x67, 0x5a, 0x6b, 0xfe, - 0x4e, 0xec, 0x3f, 0xca, 0xc5, 0x8e, 0xc7, 0xb2, 0x28, 0xf1, 0x78, 0x2c, 0x0b, 0xc9, 0x94, 0x37, - 0xf1, 0x40, 0x57, 0xf9, 0xab, 0x31, 0xe5, 0x4d, 0x50, 0x30, 0xe5, 0x4d, 0xbe, 0xeb, 0xbd, 0x95, - 0xf2, 0x0e, 0x55, 0xf9, 0x8f, 0xb3, 0x79, 0x05, 0xed, 0x4b, 0x79, 0xbe, 0x7a, 0x2b, 0xe5, 0xb9, - 0xa5, 0xf2, 0x9f, 0x64, 0xf3, 0x0a, 0x7d, 0x90, 0x92, 0xaf, 0x34, 0xdf, 0x83, 0x73, 0x7c, 0x36, - 0x9f, 0xa3, 0x06, 0x8d, 0x7c, 0xe8, 0x2f, 0xc4, 0xc6, 0x7e, 0x3a, 0x19, 0x1e, 0xb9, 0xa7, 0x62, - 0xd2, 0x58, 0x8b, 0xb6, 0xfe, 0xb5, 0x1d, 0x58, 0x87, 0x1b, 0x82, 0x74, 0x0c, 0x5b, 0x6f, 0xe4, - 0xc7, 0x6f, 0xca, 0x2f, 0xc6, 0xd6, 0x1b, 0x19, 0x89, 0xee, 0x1c, 0xf2, 0x4b, 0xb9, 0x37, 0xa3, - 0x0f, 0xbd, 0x94, 0xbf, 0x9e, 0x5a, 0x38, 0xe8, 0x80, 0xe8, 0xab, 0xb0, 0x37, 0xa3, 0x8f, 0x9a, - 0x94, 0x5f, 0x4a, 0x2d, 0x1c, 0x7c, 0x40, 0xf4, 0x05, 0x14, 0xdb, 0x22, 0x75, 0x3d, 0x9b, 0xb3, - 0x8a, 0x4c, 0x0f, 0x7f, 0x23, 0xbe, 0x45, 0x4a, 0x25, 0xc3, 0x2d, 0x52, 0x2a, 0x26, 0x8d, 0xb5, - 0xf8, 0xbc, 0x5f, 0xde, 0x81, 0xb5, 0xb4, 0xb1, 0x4b, 0xc5, 0xa4, 0xb1, 0x16, 0x1f, 0xff, 0xf5, - 0x1d, 0x58, 0x4b, 0x1b, 0xbb, 0x54, 0x0c, 0x33, 0xc7, 0x42, 0xcc, 0x5d, 0xea, 0xb8, 0xa1, 0xfa, - 0xfd, 0xa7, 0x31, 0x73, 0x2c, 0x83, 0x8e, 0x99, 0x63, 0x19, 0xa8, 0x54, 0xee, 0x42, 0x28, 0xbf, - 0xb2, 0x13, 0xf7, 0xf0, 0x5e, 0x26, 0x03, 0x95, 0xca, 0x5d, 0xc8, 0xe5, 0x6f, 0xee, 0xc4, 0x3d, - 0xbc, 0x98, 0xc9, 0x40, 0x31, 0xa3, 0xa8, 0xee, 0xe9, 0x9e, 0xd9, 0x9c, 0xb7, 0x5d, 0x4f, 0x5a, - 0xe4, 0xff, 0xb3, 0x98, 0x51, 0x94, 0x46, 0xc4, 0x8c, 0xa2, 0x34, 0x78, 0x92, 0xa9, 0x90, 0xc6, - 0xaf, 0xf6, 0x64, 0x1a, 0x5a, 0x5a, 0x69, 0xf0, 0x24, 0x53, 0x21, 0x84, 0xff, 0xbc, 0x27, 0xd3, - 0xd0, 0x53, 0x3e, 0x0d, 0xce, 0x2c, 0xd3, 0x19, 0xc7, 0xde, 0xb0, 0x6e, 0xd1, 0x0d, 0xda, 0x12, - 0x9f, 0xfe, 0x6b, 0x31, 0xcb, 0x34, 0x4e, 0x80, 0xb7, 0x28, 0x31, 0x58, 0x94, 0x91, 0xf8, 0xdc, - 0x5f, 0xcf, 0x64, 0x14, 0x1e, 0x13, 0xc5, 0x61, 0x51, 0x46, 0xe2, 0x13, 0x7f, 0x23, 0x93, 0x51, - 0x78, 0x4c, 0x14, 0x87, 0x91, 0x32, 0x8c, 0xe1, 0x5b, 0x09, 0xdd, 0xf5, 0x3d, 0x3f, 0x7f, 0x3b, - 0x17, 0xbd, 0xf5, 0x8a, 0xa2, 0xe7, 0xfb, 0xb4, 0x58, 0x01, 0x99, 0x85, 0xf8, 0xa4, 0x6f, 0x66, - 0xb0, 0x08, 0xfd, 0x1d, 0xa3, 0x10, 0x99, 0x85, 0xf8, 0x98, 0xff, 0x22, 0x83, 0x45, 0xe8, 0xf0, - 0x18, 0x85, 0x90, 0x4f, 0xc1, 0x70, 0x7d, 0x6e, 0xa5, 0xe6, 0xa7, 0xe7, 0xfb, 0x5b, 0xb9, 0xd8, - 0xab, 0xa2, 0x10, 0x87, 0xaf, 0x8a, 0xc2, 0x9f, 0x6c, 0x46, 0xb9, 0x67, 0x3b, 0x0f, 0x5a, 0xb6, - 0x6e, 0xf8, 0x11, 0x19, 0x85, 0x28, 0xfe, 0x6e, 0x6c, 0x46, 0x49, 0x27, 0x63, 0x33, 0x4a, 0x3a, - 0x26, 0x8d, 0xb5, 0x10, 0xd1, 0xb7, 0x76, 0x60, 0x1d, 0xce, 0x83, 0xe9, 0x98, 0x34, 0xd6, 0x42, - 0x74, 0x7f, 0x6f, 0x07, 0xd6, 0xe1, 0x3c, 0x98, 0x8e, 0x21, 0x14, 0x2e, 0x04, 0x6f, 0x0e, 0xc3, - 0x4d, 0x60, 0xd5, 0x7a, 0xc8, 0x36, 0x9a, 0xca, 0xdf, 0x8f, 0x1d, 0x2f, 0x64, 0x93, 0xce, 0xf7, - 0x69, 0x3d, 0x18, 0x4d, 0x0f, 0x40, 0x3f, 0x1e, 0x61, 0xdf, 0x2a, 0x0d, 0x7e, 0x23, 0x37, 0xf1, - 0x9b, 0xb9, 0x5b, 0xa5, 0xc1, 0xdf, 0xcc, 0x4d, 0xfc, 0x16, 0xfb, 0xff, 0xb7, 0x72, 0x13, 0xbf, - 0x9d, 0xd3, 0x9e, 0x08, 0xa7, 0xa3, 0xf2, 0x1a, 0xb5, 0xbc, 0x5a, 0x4b, 0x17, 0x93, 0x69, 0x2a, - 0x8a, 0xff, 0x4c, 0x45, 0x89, 0x14, 0x64, 0x5f, 0xcb, 0xc1, 0x48, 0xdd, 0x73, 0xa8, 0xde, 0x16, - 0x11, 0xfd, 0x2e, 0xc0, 0x20, 0x77, 0x3a, 0xf7, 0x5f, 0xc8, 0x6b, 0xc1, 0x6f, 0x72, 0x19, 0xc6, - 0x16, 0x74, 0xd7, 0xc3, 0x26, 0x56, 0x2d, 0x83, 0x3e, 0xc2, 0x07, 0x97, 0x05, 0x2d, 0x06, 0x25, - 0x0b, 0x9c, 0x8e, 0x97, 0xc3, 0x20, 0xae, 0x85, 0x1d, 0x03, 0xd9, 0x0d, 0x7e, 0x7b, 0x6b, 0xb2, - 0x0f, 0xe3, 0xd6, 0xc5, 0xca, 0xaa, 0xbf, 0x9b, 0x83, 0x84, 0x3b, 0xfc, 0xfe, 0x23, 0x57, 0x2c, - 0xc3, 0x78, 0x2c, 0x70, 0xb0, 0x78, 0x35, 0xba, 0xcb, 0xb8, 0xc2, 0xf1, 0xd2, 0xe4, 0xe3, 0xc1, - 0x6b, 0xc5, 0x3b, 0xda, 0x82, 0x08, 0x52, 0x88, 0xe9, 0x35, 0xba, 0x4e, 0x4b, 0x93, 0x50, 0x22, - 0x08, 0xd5, 0x77, 0x27, 0xc2, 0xa8, 0xa8, 0xe4, 0xb2, 0x08, 0xa3, 0x91, 0x0b, 0x43, 0x1b, 0xc6, - 0x92, 0xf9, 0xf3, 0xb0, 0x19, 0xdf, 0x07, 0x23, 0xd5, 0x76, 0x87, 0x3a, 0xae, 0x6d, 0xe9, 0x9e, - 0xed, 0x88, 0x28, 0x04, 0x18, 0xf6, 0xce, 0x94, 0xe0, 0x72, 0x28, 0x36, 0x99, 0x9e, 0x5c, 0xf5, - 0x33, 0x04, 0x16, 0x30, 0x1e, 0x2d, 0x3e, 0x25, 0x8e, 0x27, 0x88, 0xe7, 0x14, 0x8c, 0xf4, 0x8e, - 0xab, 0xe3, 0xbb, 0xd6, 0x80, 0xb4, 0xcb, 0x00, 0x32, 0x29, 0x52, 0x90, 0x17, 0xa0, 0x84, 0x7a, - 0xec, 0x62, 0xe6, 0x4f, 0x11, 0x70, 0xb1, 0x85, 0x10, 0x39, 0xbc, 0x1d, 0xa7, 0x21, 0xb7, 0x61, - 0x22, 0x74, 0x72, 0xb8, 0xe9, 0xd8, 0xdd, 0x8e, 0x9f, 0xeb, 0x07, 0x13, 0xeb, 0x3f, 0x08, 0x70, - 0x8d, 0x35, 0x44, 0x4a, 0x2c, 0x12, 0x05, 0xc9, 0x3c, 0x8c, 0x87, 0x30, 0x26, 0x22, 0x3f, 0xc7, - 0x18, 0xe6, 0x77, 0x95, 0x78, 0x31, 0x71, 0x46, 0xf2, 0xbb, 0xc6, 0x8a, 0x91, 0x2a, 0x0c, 0xf8, - 0xd1, 0x16, 0x07, 0x77, 0x54, 0xd2, 0xd3, 0x22, 0xda, 0xe2, 0x80, 0x1c, 0x67, 0xd1, 0x2f, 0x4f, - 0xe6, 0x60, 0x4c, 0xb3, 0xbb, 0x1e, 0x5d, 0xb1, 0xc5, 0xe9, 0x80, 0x88, 0xea, 0x89, 0x6d, 0x72, - 0x18, 0xa6, 0xe1, 0xd9, 0x8d, 0x26, 0xc7, 0xc9, 0xf9, 0xf1, 0xa3, 0xa5, 0xc8, 0x12, 0x9c, 0x4a, - 0xb8, 0x83, 0xe0, 0x33, 0xd8, 0x21, 0x1e, 0x37, 0x4f, 0xfa, 0xbc, 0x24, 0xb3, 0x64, 0x51, 0xf2, - 0xa3, 0x39, 0x28, 0xad, 0x38, 0xba, 0xe9, 0xb9, 0xe2, 0x49, 0xec, 0xd9, 0xa9, 0x0d, 0x47, 0xef, - 0x30, 0xfd, 0x98, 0xc2, 0x80, 0xc3, 0x77, 0xf5, 0x56, 0x97, 0xba, 0xd3, 0xf7, 0xd8, 0xd7, 0xfd, - 0xf7, 0x5b, 0x93, 0x6f, 0xae, 0xe1, 0xa1, 0xf3, 0x54, 0xd3, 0x6e, 0x5f, 0x5b, 0x73, 0xf4, 0x87, - 0xa6, 0x87, 0xa6, 0xbd, 0xde, 0xba, 0xe6, 0xd1, 0x16, 0x9e, 0x6d, 0x5f, 0xd3, 0x3b, 0xe6, 0x35, - 0x0c, 0x6c, 0x7f, 0x2d, 0xe0, 0xc4, 0x6b, 0x60, 0x2a, 0xe0, 0xe1, 0x5f, 0xb2, 0x0a, 0x70, 0x1c, - 0x59, 0x02, 0x10, 0x9f, 0x5a, 0xee, 0x74, 0xc4, 0xfb, 0x5a, 0xe9, 0x44, 0xd8, 0xc7, 0x70, 0xc5, - 0x0e, 0x04, 0xa6, 0x77, 0xa4, 0x60, 0xce, 0x9a, 0xc4, 0x81, 0x69, 0xc1, 0x8a, 0x68, 0x91, 0x2f, - 0xa6, 0xd1, 0x50, 0xe2, 0x7e, 0x63, 0x53, 0x84, 0x14, 0x2f, 0x46, 0x56, 0x61, 0x5c, 0xf0, 0x0d, - 0x52, 0xbf, 0x8c, 0x45, 0x67, 0x85, 0x18, 0x9a, 0x2b, 0x6d, 0xd0, 0x46, 0x43, 0x80, 0xe5, 0x3a, - 0x62, 0x25, 0xc8, 0x74, 0x98, 0xaa, 0x7a, 0x49, 0x6f, 0x53, 0x57, 0x19, 0x47, 0x8d, 0xbd, 0xb8, - 0xbd, 0x35, 0xa9, 0xf8, 0xe5, 0x31, 0xf0, 0xa8, 0x2c, 0xba, 0x68, 0x11, 0x99, 0x07, 0xd7, 0xfa, - 0x89, 0x14, 0x1e, 0x71, 0x9d, 0x8f, 0x16, 0x21, 0x33, 0x30, 0x1a, 0x3c, 0xef, 0xb9, 0x73, 0xa7, - 0x5a, 0xc1, 0x07, 0xbc, 0x22, 0xf6, 0x6c, 0x2c, 0x39, 0x8b, 0xcc, 0x24, 0x52, 0x46, 0x8a, 0x89, - 0xc2, 0x5f, 0xf4, 0xc6, 0x62, 0xa2, 0x74, 0x52, 0x62, 0xa2, 0xd4, 0xc8, 0xdb, 0x30, 0x5c, 0xbe, - 0x57, 0x17, 0xb1, 0x5e, 0x5c, 0xe5, 0x74, 0x98, 0xe9, 0x4b, 0xdf, 0x70, 0x1b, 0x7e, 0x5c, 0x18, - 0xb9, 0xe9, 0x32, 0x3d, 0x99, 0x85, 0xb1, 0x88, 0x87, 0xa0, 0xab, 0x9c, 0x41, 0x0e, 0xd8, 0x72, - 0x1d, 0x31, 0x0d, 0x47, 0xa0, 0xe4, 0xe1, 0x15, 0x2d, 0xc4, 0xb4, 0xa6, 0x62, 0xba, 0x98, 0x35, - 0x49, 0xa3, 0x18, 0x56, 0x06, 0x9f, 0x03, 0x0f, 0x72, 0xad, 0x31, 0x04, 0xaa, 0xe1, 0x70, 0x9c, - 0xdc, 0xa3, 0xb1, 0x62, 0xe4, 0x7d, 0x20, 0x98, 0x67, 0x89, 0x1a, 0xfe, 0x85, 0x71, 0xb5, 0xe2, - 0x2a, 0xe7, 0x30, 0xf0, 0x3a, 0x89, 0x87, 0xb1, 0xa8, 0x56, 0xa6, 0x2f, 0x8b, 0xe9, 0xe3, 0x29, - 0x9d, 0x97, 0x6a, 0xf8, 0x21, 0x2c, 0x1a, 0x66, 0x24, 0x09, 0x75, 0x0a, 0x57, 0xb2, 0x01, 0xe7, - 0x6b, 0x0e, 0x7d, 0x68, 0xda, 0x5d, 0xd7, 0x5f, 0x3e, 0xfc, 0x79, 0xeb, 0xfc, 0x8e, 0xf3, 0xd6, - 0x33, 0xa2, 0xe2, 0xb3, 0x1d, 0x87, 0x3e, 0x6c, 0xf8, 0xe1, 0xb6, 0x23, 0xd1, 0x62, 0xb3, 0xb8, - 0x63, 0x2a, 0xed, 0x0f, 0xba, 0x0e, 0x15, 0x70, 0x93, 0xba, 0x8a, 0x12, 0x4e, 0xb5, 0x3c, 0x42, - 0x90, 0x19, 0xe0, 0x22, 0xa9, 0xb4, 0xa3, 0xc5, 0x88, 0x06, 0xe4, 0xe6, 0x8c, 0xef, 0x3c, 0x50, - 0x6e, 0xf2, 0x84, 0xc3, 0xca, 0x13, 0xc8, 0x4c, 0x65, 0x62, 0x59, 0x6b, 0x06, 0xa1, 0xf7, 0x1b, - 0xba, 0xc0, 0xcb, 0x62, 0x49, 0x96, 0x26, 0x0b, 0x30, 0x51, 0x73, 0xf0, 0x28, 0xf3, 0x36, 0xdd, - 0xac, 0xd9, 0x2d, 0xb3, 0xb9, 0x89, 0xaf, 0x92, 0xc5, 0x54, 0xd9, 0xe1, 0xb8, 0xc6, 0x03, 0xba, - 0xd9, 0xe8, 0x20, 0x56, 0x5e, 0x56, 0xe2, 0x25, 0xe5, 0x50, 0xd8, 0x4f, 0xee, 0x2e, 0x14, 0x36, - 0x85, 0x09, 0xe1, 0x7a, 0xf0, 0xc8, 0xa3, 0x16, 0x5b, 0xea, 0x5d, 0xf1, 0x02, 0x59, 0x89, 0xb9, - 0x2a, 0x04, 0x78, 0x3e, 0x75, 0x88, 0x51, 0x46, 0x03, 0xb0, 0xdc, 0xb0, 0x78, 0x91, 0x64, 0xbc, - 0xe8, 0x4b, 0xfb, 0x88, 0x17, 0xfd, 0xbf, 0x17, 0xe4, 0xf9, 0x97, 0x5c, 0x84, 0xa2, 0x94, 0xce, - 0x09, 0x83, 0xe1, 0x62, 0xe8, 0xfb, 0xa2, 0x88, 0xf1, 0x3d, 0x24, 0x6c, 0x97, 0x20, 0xea, 0x11, - 0xe6, 0xef, 0x0c, 0x03, 0xa4, 0x6a, 0x21, 0x01, 0xe6, 0x4e, 0xec, 0xae, 0xb6, 0xcc, 0x26, 0x26, - 0x44, 0x28, 0x48, 0x61, 0x4e, 0x10, 0xca, 0xf3, 0x21, 0x48, 0x24, 0xe4, 0x3a, 0x0c, 0xfb, 0x47, - 0xe8, 0x61, 0x30, 0x68, 0x8c, 0x93, 0x2f, 0x66, 0x6b, 0x11, 0x86, 0x5f, 0x22, 0x22, 0x6f, 0x00, - 0x84, 0xd3, 0x81, 0xb0, 0xb4, 0x70, 0xa9, 0x90, 0x67, 0x0f, 0x79, 0xa9, 0x08, 0xa9, 0xd9, 0xc4, - 0x29, 0xab, 0xa3, 0x9f, 0x2d, 0x16, 0x27, 0xce, 0x88, 0x0e, 0xcb, 0x0a, 0x12, 0x2d, 0x42, 0x96, - 0xe1, 0x54, 0x42, 0x03, 0x45, 0xe8, 0xe8, 0x67, 0xb6, 0xb7, 0x26, 0x2f, 0xa5, 0xa8, 0xaf, 0xbc, - 0x30, 0x27, 0xca, 0x92, 0x67, 0xa1, 0x70, 0x47, 0xab, 0x8a, 0xf0, 0xb5, 0x3c, 0xf2, 0x71, 0x24, - 0xb6, 0x15, 0xc3, 0x92, 0xd7, 0x01, 0x78, 0x7a, 0x98, 0x9a, 0xed, 0x78, 0x68, 0x51, 0x8c, 0x4e, - 0x3f, 0xc1, 0xc6, 0x32, 0x4f, 0x1f, 0xd3, 0x60, 0xcb, 0x98, 0xfc, 0xd1, 0x21, 0xb1, 0xfa, 0xa7, - 0xf3, 0x89, 0x65, 0x8d, 0x09, 0x5e, 0xb4, 0x42, 0xea, 0x7c, 0x14, 0xbc, 0xdf, 0x74, 0x2e, 0x78, - 0x89, 0x88, 0x5c, 0x81, 0xc1, 0x1a, 0x9b, 0x54, 0x9a, 0x76, 0x4b, 0xa8, 0x02, 0xc6, 0x30, 0xeb, - 0x08, 0x98, 0x16, 0x60, 0xc9, 0x75, 0x29, 0x3f, 0xb2, 0x14, 0x4c, 0xde, 0xcf, 0x8f, 0x1c, 0x8f, - 0xaa, 0x8e, 0x99, 0x92, 0xaf, 0xc7, 0xf2, 0xad, 0x89, 0x32, 0x29, 0x4b, 0x6a, 0x98, 0x5f, 0x2d, - 0x30, 0x68, 0xfb, 0x77, 0x32, 0x68, 0xd5, 0xbf, 0x9d, 0x4b, 0x0e, 0x51, 0x72, 0x23, 0x19, 0xd7, - 0x19, 0xd7, 0xaf, 0x00, 0x28, 0xd7, 0x1a, 0x44, 0x78, 0x8e, 0x44, 0x68, 0xce, 0xef, 0x3b, 0x42, - 0x73, 0x61, 0x8f, 0x11, 0x9a, 0xd5, 0xff, 0xb7, 0xd8, 0xd3, 0xcb, 0xfe, 0x48, 0x22, 0xf9, 0xbd, - 0xce, 0x36, 0x65, 0xac, 0xf6, 0xb2, 0x9b, 0xd8, 0x5a, 0x70, 0x27, 0xe2, 0x86, 0xce, 0x47, 0xa5, - 0xab, 0x45, 0x29, 0xc9, 0x3b, 0x30, 0xe2, 0x7f, 0x00, 0x46, 0xfe, 0x96, 0x22, 0x56, 0x07, 0x0b, - 0x62, 0x2c, 0x46, 0x76, 0xa4, 0x00, 0x79, 0x05, 0x86, 0xd0, 0x1c, 0xea, 0xe8, 0x4d, 0x3f, 0x2c, - 0x3c, 0x8f, 0x23, 0xef, 0x03, 0xe5, 0x68, 0x75, 0x01, 0x25, 0xf9, 0x1c, 0x94, 0x44, 0x6e, 0x94, - 0x12, 0x2e, 0xd1, 0xd7, 0x76, 0xf1, 0x2c, 0x61, 0x4a, 0xce, 0x8b, 0xc2, 0x37, 0x38, 0x08, 0x88, - 0x6c, 0x70, 0x78, 0x4a, 0x94, 0x15, 0x38, 0x5d, 0x73, 0xa8, 0x81, 0x0f, 0x60, 0x66, 0x1f, 0x75, - 0x1c, 0x91, 0xb5, 0x86, 0x4f, 0x10, 0xb8, 0xbe, 0x75, 0x7c, 0x34, 0x5b, 0x79, 0x05, 0x5e, 0x8e, - 0x4d, 0x9d, 0x52, 0x9c, 0x19, 0x3d, 0xbc, 0x25, 0xb7, 0xe9, 0xe6, 0x86, 0xed, 0x18, 0x3c, 0xb1, - 0x8b, 0x98, 0xfa, 0x85, 0xa0, 0x1f, 0x08, 0x94, 0x6c, 0xf4, 0x44, 0x0b, 0x5d, 0x78, 0x1d, 0x86, - 0xf7, 0x9b, 0x5b, 0xe4, 0x57, 0xf2, 0x19, 0xef, 0xd5, 0x1e, 0xdf, 0xf4, 0x8e, 0x41, 0xce, 0xf1, - 0xfe, 0x8c, 0x9c, 0xe3, 0x7f, 0x9c, 0xcf, 0x78, 0x8c, 0xf7, 0x58, 0xe7, 0x06, 0x0e, 0x84, 0x11, - 0xcd, 0x0d, 0x1c, 0xa6, 0x65, 0x36, 0x0d, 0x4d, 0x26, 0x8a, 0x65, 0x11, 0x2f, 0xed, 0x98, 0x45, - 0xfc, 0x17, 0x0a, 0xbd, 0x1e, 0x2b, 0x9e, 0xc8, 0x7e, 0x2f, 0xb2, 0xbf, 0x0e, 0xc3, 0x81, 0x64, - 0xab, 0x15, 0xb4, 0x97, 0x46, 0x83, 0x4c, 0x46, 0x1c, 0x8c, 0x65, 0x24, 0x22, 0x72, 0x95, 0xb7, - 0xb5, 0x6e, 0x7e, 0xc0, 0x73, 0x6a, 0x8c, 0x8a, 0x6c, 0x09, 0xba, 0xa7, 0x37, 0x5c, 0xf3, 0x03, - 0xaa, 0x05, 0x68, 0xf5, 0xef, 0xe6, 0x53, 0x5f, 0x7c, 0x9e, 0xf4, 0xd1, 0x1e, 0xfa, 0x28, 0x45, - 0x88, 0xfc, 0xad, 0xea, 0x89, 0x10, 0xf7, 0x20, 0xc4, 0x3f, 0xca, 0xa7, 0xbe, 0xec, 0x3d, 0x11, - 0xe2, 0x5e, 0x66, 0x8b, 0x17, 0x60, 0x48, 0xb3, 0x37, 0xdc, 0x19, 0xdc, 0x13, 0xf1, 0xb9, 0x02, - 0x27, 0x6a, 0xc7, 0xde, 0x70, 0x1b, 0xb8, 0xdb, 0xd1, 0x42, 0x02, 0xf5, 0xbb, 0xf9, 0x1e, 0x6f, - 0x9f, 0x4f, 0x04, 0xff, 0x61, 0x2e, 0x91, 0xbf, 0x9e, 0x8f, 0xbc, 0xad, 0x7e, 0x7c, 0x85, 0x7d, - 0x0d, 0xa0, 0xde, 0x5c, 0xa7, 0x6d, 0x5d, 0xca, 0x4b, 0x86, 0x47, 0x16, 0x2e, 0x42, 0x45, 0x3e, - 0xeb, 0x90, 0x44, 0xfd, 0x46, 0x3e, 0xf6, 0xb8, 0xfc, 0x44, 0x76, 0xbb, 0x96, 0x5d, 0xa0, 0x75, - 0xe2, 0xbd, 0xfc, 0x89, 0xe4, 0x76, 0x2b, 0xb9, 0x1f, 0xcb, 0xc7, 0x42, 0x0b, 0x3c, 0xb6, 0xb2, - 0x63, 0x03, 0x30, 0x19, 0xf2, 0xe0, 0xb1, 0xd5, 0xa4, 0x17, 0x60, 0x48, 0xc8, 0x21, 0x58, 0x2a, - 0xf8, 0xbc, 0xcf, 0x81, 0x78, 0x40, 0x1b, 0x10, 0xa8, 0x7f, 0x26, 0x0f, 0xd1, 0x90, 0x0f, 0x8f, - 0xa9, 0x0e, 0xfd, 0x7a, 0x3e, 0x1a, 0xec, 0xe2, 0xf1, 0xd5, 0x9f, 0x29, 0x80, 0x7a, 0x77, 0xb5, - 0x29, 0x62, 0x25, 0xf7, 0x4b, 0x27, 0xfc, 0x01, 0x54, 0x93, 0x28, 0xd4, 0x7f, 0x95, 0x4f, 0x8d, - 0xc0, 0xf1, 0xf8, 0x0a, 0xf0, 0x65, 0x3c, 0x15, 0x6f, 0x5a, 0xe1, 0x44, 0x8e, 0x87, 0x90, 0x6c, - 0xfc, 0x25, 0x92, 0x59, 0xfa, 0x84, 0xe4, 0x53, 0x29, 0xe6, 0x1a, 0xa6, 0xda, 0x08, 0xcd, 0x35, - 0xf9, 0x30, 0x5f, 0x32, 0xdc, 0x7e, 0x27, 0xbf, 0x53, 0xc0, 0x92, 0xc7, 0x79, 0x55, 0x1d, 0xa8, - 0xe9, 0x9b, 0x18, 0x58, 0x93, 0xf5, 0xc4, 0x08, 0x4f, 0xb5, 0xd8, 0xe1, 0x20, 0xf9, 0xda, 0x4e, - 0x50, 0xa9, 0xff, 0xbc, 0x3f, 0x3d, 0x5a, 0xc6, 0xe3, 0x2b, 0xc2, 0x8b, 0x50, 0xac, 0xe9, 0xde, - 0xba, 0xd0, 0x64, 0xbc, 0x0d, 0xec, 0xe8, 0xde, 0xba, 0x86, 0x50, 0x72, 0x15, 0x06, 0x35, 0x7d, - 0x83, 0x9f, 0x79, 0x96, 0xc2, 0x34, 0x98, 0x8e, 0xbe, 0xd1, 0xe0, 0xe7, 0x9e, 0x01, 0x9a, 0xa8, - 0x41, 0x1a, 0x56, 0x7e, 0xf2, 0x8d, 0x39, 0x00, 0x79, 0x1a, 0xd6, 0x20, 0xf9, 0xea, 0x45, 0x28, - 0x4e, 0xdb, 0xc6, 0x26, 0xde, 0x7c, 0x8d, 0xf0, 0xca, 0x56, 0x6d, 0x63, 0x53, 0x43, 0x28, 0xf9, - 0xf1, 0x1c, 0x0c, 0xcc, 0x53, 0xdd, 0x60, 0x23, 0x64, 0xa8, 0x97, 0xc3, 0xca, 0x67, 0x0e, 0xc7, - 0x61, 0xe5, 0xd4, 0x3a, 0xaf, 0x4c, 0x56, 0x14, 0x51, 0x3f, 0xb9, 0x09, 0x83, 0x33, 0xba, 0x47, - 0xd7, 0x6c, 0x67, 0x13, 0x5d, 0x70, 0xc6, 0xc2, 0x17, 0x17, 0x11, 0xfd, 0xf1, 0x89, 0xf8, 0xcd, - 0x58, 0x53, 0xfc, 0xd2, 0x82, 0xc2, 0x4c, 0x2c, 0xfc, 0x66, 0x4e, 0xa4, 0x1c, 0x47, 0xb1, 0xf0, - 0x2b, 0x3c, 0x4d, 0x60, 0xc2, 0x63, 0xe5, 0x91, 0xf4, 0x63, 0x65, 0xb4, 0x1e, 0xd1, 0x4d, 0x0f, - 0x93, 0x9f, 0x8e, 0xe2, 0xa2, 0xcf, 0xad, 0x47, 0x84, 0x62, 0xee, 0x53, 0x4d, 0x22, 0x51, 0xbf, - 0xd3, 0x0f, 0xa9, 0x6f, 0xeb, 0x4f, 0x94, 0xfc, 0x44, 0xc9, 0x43, 0x25, 0xaf, 0x24, 0x94, 0xfc, - 0x42, 0x32, 0x5a, 0xc3, 0x47, 0x54, 0xc3, 0xbf, 0x5a, 0x4c, 0xc4, 0x7a, 0x79, 0xbc, 0x77, 0x97, - 0xa1, 0xf4, 0xfa, 0x77, 0x94, 0x5e, 0x30, 0x20, 0x4a, 0x3b, 0x0e, 0x88, 0x81, 0xdd, 0x0e, 0x88, - 0xc1, 0xcc, 0x01, 0x11, 0x2a, 0xc8, 0x50, 0xa6, 0x82, 0x54, 0xc5, 0xa0, 0x81, 0xde, 0x29, 0x67, - 0x2e, 0x6e, 0x6f, 0x4d, 0x8e, 0xb1, 0xd1, 0x94, 0x9a, 0x6b, 0x06, 0x59, 0xa8, 0xbf, 0x5b, 0xec, - 0x11, 0xa0, 0xe9, 0x48, 0x74, 0xe4, 0x65, 0x28, 0x94, 0x3b, 0x1d, 0xa1, 0x1f, 0xa7, 0xa5, 0xd8, - 0x50, 0x19, 0xa5, 0x18, 0x35, 0x79, 0x03, 0x0a, 0xe5, 0x7b, 0xf5, 0x78, 0x9a, 0x99, 0xf2, 0xbd, - 0xba, 0xf8, 0x92, 0xcc, 0xb2, 0xf7, 0xea, 0xe4, 0xad, 0x30, 0xde, 0xeb, 0x7a, 0xd7, 0x7a, 0x20, - 0x36, 0x8a, 0xc2, 0x53, 0xd7, 0xf7, 0xe4, 0x69, 0x32, 0x14, 0xdb, 0x2e, 0xc6, 0x68, 0x63, 0xda, - 0x54, 0xda, 0xbd, 0x36, 0x0d, 0xec, 0xa8, 0x4d, 0x83, 0xbb, 0xd5, 0xa6, 0xa1, 0x5d, 0x68, 0x13, - 0xec, 0xa8, 0x4d, 0xc3, 0x07, 0xd7, 0xa6, 0x0e, 0x5c, 0x48, 0x06, 0xd5, 0x0b, 0x34, 0x42, 0x03, - 0x92, 0xc4, 0x0a, 0xc7, 0x12, 0xbc, 0xfa, 0xef, 0x72, 0x6c, 0x63, 0x03, 0xd1, 0x0d, 0x97, 0xe1, - 0x65, 0xd7, 0xb6, 0x64, 0x69, 0xf5, 0x57, 0xf2, 0xd9, 0xb1, 0x00, 0x8f, 0xe7, 0x14, 0xf7, 0x83, - 0xa9, 0x52, 0x2a, 0xc6, 0x1e, 0x4f, 0x64, 0x4a, 0x39, 0xc6, 0x36, 0x4d, 0x66, 0x5f, 0xcf, 0x67, - 0x05, 0x28, 0x3c, 0x90, 0xc4, 0x3e, 0x96, 0x74, 0x86, 0x43, 0x17, 0x7f, 0x37, 0xea, 0x05, 0x37, - 0x07, 0x23, 0xb2, 0x10, 0x85, 0x94, 0x76, 0x23, 0xe0, 0x48, 0x39, 0xf2, 0x56, 0x90, 0x0d, 0x48, - 0xf2, 0x8f, 0x41, 0x4f, 0x37, 0x7f, 0xcc, 0xc6, 0xdc, 0x63, 0x64, 0x72, 0xf2, 0x02, 0x94, 0xe6, - 0x30, 0xbc, 0xbe, 0x3c, 0xd8, 0x79, 0xc0, 0x7d, 0xd9, 0x6b, 0x85, 0xd3, 0xa8, 0x7f, 0x3b, 0x07, - 0xa7, 0x6f, 0x77, 0x57, 0xa9, 0x70, 0xb4, 0x0b, 0xda, 0xf0, 0x3e, 0x00, 0x03, 0x0b, 0x87, 0x99, - 0x1c, 0x3a, 0xcc, 0x7c, 0x42, 0x0e, 0x64, 0x18, 0x2b, 0x30, 0x15, 0x52, 0x73, 0x67, 0x99, 0x4b, - 0xbe, 0xcf, 0xe9, 0x83, 0xee, 0x2a, 0x6d, 0x24, 0xbc, 0x66, 0x24, 0xee, 0x17, 0xde, 0xe6, 0xde, - 0xfc, 0xfb, 0x75, 0x50, 0xf9, 0xe5, 0x7c, 0x66, 0xec, 0xc8, 0x63, 0x9b, 0xe4, 0xf4, 0xfb, 0x53, - 0x7b, 0x25, 0x9e, 0xec, 0x34, 0x85, 0x24, 0xc6, 0x31, 0x8d, 0x4b, 0xba, 0xc0, 0x8e, 0x79, 0xea, - 0xdd, 0x0f, 0x55, 0x60, 0x7f, 0x90, 0xcb, 0x8c, 0xf1, 0x79, 0x5c, 0x05, 0xa6, 0xfe, 0x2f, 0x05, - 0x3f, 0xb4, 0xe8, 0x81, 0x3e, 0xe1, 0x05, 0x18, 0x12, 0x11, 0x16, 0xa2, 0x7e, 0xc2, 0xe2, 0xd8, - 0x10, 0x8f, 0xa1, 0x03, 0x02, 0x66, 0x52, 0x48, 0x4e, 0xcc, 0x92, 0x9f, 0xb0, 0xe4, 0xc0, 0xac, - 0x49, 0x24, 0xcc, 0x68, 0x98, 0x7d, 0x64, 0x7a, 0x68, 0x81, 0xb0, 0xbe, 0x2c, 0x70, 0xa3, 0x81, - 0x3e, 0x32, 0x3d, 0x6e, 0x7f, 0x04, 0x68, 0x66, 0x10, 0x70, 0x5b, 0x44, 0xcc, 0x7b, 0x68, 0x10, - 0x70, 0x53, 0x45, 0x13, 0x18, 0xd6, 0x5a, 0xe1, 0x7c, 0x2b, 0x5c, 0x5a, 0x44, 0x6b, 0x85, 0xbb, - 0x2e, 0xb6, 0x36, 0x20, 0x60, 0x1c, 0x35, 0xba, 0x16, 0x3a, 0xf1, 0x21, 0x47, 0x07, 0x21, 0x9a, - 0xc0, 0x90, 0xeb, 0x30, 0x56, 0xf7, 0x74, 0xcb, 0xd0, 0x1d, 0x63, 0xb9, 0xeb, 0x75, 0xba, 0x9e, - 0x6c, 0x00, 0xbb, 0x9e, 0x61, 0x77, 0x3d, 0x2d, 0x46, 0x41, 0x3e, 0x09, 0xa3, 0x3e, 0x64, 0xd6, - 0x71, 0x6c, 0x47, 0xb6, 0x72, 0x5c, 0xcf, 0xa0, 0x8e, 0xa3, 0x45, 0x09, 0xc8, 0xa7, 0x60, 0xb4, - 0x6a, 0x3d, 0xb4, 0x9b, 0x3c, 0xca, 0x80, 0xb6, 0x20, 0x6c, 0x1e, 0x7c, 0x31, 0x66, 0x06, 0x88, - 0x46, 0xd7, 0x69, 0x69, 0x51, 0x42, 0x75, 0x3b, 0x9f, 0x8c, 0xc0, 0xfa, 0xf8, 0x6e, 0x90, 0xae, - 0x46, 0x1d, 0xf7, 0xd0, 0x5b, 0x15, 0x8d, 0x4f, 0xd9, 0x6f, 0x98, 0xdb, 0xa0, 0xd7, 0x61, 0xf0, - 0x36, 0xdd, 0xe4, 0x3e, 0xa6, 0xa5, 0xd0, 0x2d, 0xf9, 0x81, 0x80, 0xc9, 0xa7, 0xbb, 0x3e, 0x9d, - 0xfa, 0xcd, 0x7c, 0x32, 0xb6, 0xec, 0xe3, 0x2b, 0xec, 0x4f, 0xc2, 0x00, 0x8a, 0xb2, 0xea, 0x5f, - 0x2f, 0xa0, 0x00, 0x51, 0xdc, 0x51, 0x6f, 0x67, 0x9f, 0x4c, 0xfd, 0xf9, 0x52, 0x3c, 0xe0, 0xf0, - 0xe3, 0x2b, 0xbd, 0x37, 0x61, 0x78, 0xc6, 0xb6, 0x5c, 0xd3, 0xf5, 0xa8, 0xd5, 0xf4, 0x15, 0x16, - 0x1d, 0xff, 0x9b, 0x21, 0x58, 0xb6, 0x01, 0x25, 0xea, 0xfd, 0x28, 0x2f, 0x79, 0x15, 0x86, 0x50, - 0xe4, 0x68, 0x73, 0xf2, 0x09, 0x0f, 0x6f, 0x26, 0x56, 0x19, 0x30, 0x6e, 0x71, 0x86, 0xa4, 0xe4, - 0x0e, 0x0c, 0xce, 0xac, 0x9b, 0x2d, 0xc3, 0xa1, 0x16, 0xfa, 0x26, 0x4b, 0x71, 0x5d, 0xa2, 0x7d, - 0x39, 0x85, 0xff, 0x22, 0x2d, 0x6f, 0x4e, 0x53, 0x14, 0x8b, 0x3c, 0x16, 0x13, 0xb0, 0x0b, 0x3f, - 0x9d, 0x07, 0x08, 0x0b, 0x90, 0xa7, 0x21, 0x1f, 0xe4, 0xec, 0x46, 0x97, 0x98, 0x88, 0x06, 0xe5, - 0x71, 0xa9, 0x10, 0x63, 0x3b, 0xbf, 0xe3, 0xd8, 0xbe, 0x03, 0x25, 0x7e, 0xba, 0x86, 0x5e, 0xeb, - 0x52, 0x0c, 0xd4, 0xcc, 0x06, 0x4f, 0x21, 0x3d, 0xb7, 0xa5, 0xd1, 0xf2, 0x8c, 0x78, 0x80, 0x73, - 0x66, 0x17, 0x9a, 0xd0, 0x8f, 0x7f, 0x91, 0xcb, 0x50, 0x5c, 0xf1, 0xf3, 0xfd, 0x8e, 0xf2, 0x59, - 0x3a, 0x26, 0x3f, 0xc4, 0xb3, 0x6e, 0x9a, 0xb1, 0x2d, 0x8f, 0x55, 0x8d, 0xad, 0x1e, 0x11, 0x72, - 0x11, 0xb0, 0x88, 0x5c, 0x04, 0x4c, 0xfd, 0xaf, 0xf2, 0x29, 0xa1, 0xb0, 0x1f, 0xdf, 0x61, 0xf2, - 0x3a, 0x00, 0xbe, 0x3c, 0x67, 0xf2, 0xf4, 0x9f, 0x83, 0xe0, 0x28, 0x41, 0x46, 0xa8, 0xb6, 0x91, - 0x6d, 0x47, 0x48, 0xac, 0xfe, 0x83, 0x5c, 0x22, 0x7e, 0xf2, 0x81, 0xe4, 0x28, 0x5b, 0x65, 0xf9, - 0x7d, 0x9a, 0xb1, 0x7e, 0x5f, 0x14, 0xf6, 0xd6, 0x17, 0xd1, 0x6f, 0x39, 0x04, 0xcb, 0xf4, 0x28, - 0xbf, 0xe5, 0x3b, 0xf9, 0xb4, 0x68, 0xd2, 0xc7, 0x53, 0xc5, 0x6f, 0x04, 0x46, 0x69, 0x31, 0x16, - 0xbf, 0x1f, 0xa1, 0xf1, 0x9c, 0xe4, 0xc2, 0x4c, 0xfd, 0x3c, 0x8c, 0xc7, 0x62, 0x2c, 0x8b, 0xf4, - 0xd0, 0x97, 0x7b, 0x07, 0x6b, 0xce, 0x8e, 0x59, 0x10, 0x21, 0x53, 0xff, 0xbf, 0x5c, 0xef, 0x08, - 0xdb, 0x47, 0xae, 0x3a, 0x29, 0x02, 0x28, 0xfc, 0xeb, 0x11, 0xc0, 0x21, 0x6c, 0x83, 0x8f, 0xb7, - 0x00, 0x3e, 0x22, 0x93, 0xc7, 0x87, 0x2d, 0x80, 0x9f, 0xcf, 0xed, 0x18, 0x20, 0xfd, 0xa8, 0x65, - 0xa0, 0xfe, 0x8f, 0xb9, 0xd4, 0x40, 0xe6, 0x07, 0x6a, 0xd7, 0x5b, 0x50, 0xe2, 0x2e, 0x3c, 0xa2, - 0x55, 0x52, 0xea, 0x37, 0x06, 0xcd, 0x28, 0x2f, 0xca, 0x90, 0x05, 0x18, 0xe0, 0x6d, 0x30, 0x44, - 0x6f, 0x3c, 0xd7, 0x23, 0x9a, 0xba, 0x91, 0x35, 0x39, 0x0a, 0xb4, 0xfa, 0x77, 0x72, 0x89, 0xb8, - 0xea, 0x47, 0xf8, 0x6d, 0xe1, 0x54, 0x5d, 0xd8, 0xfd, 0x54, 0xad, 0xfe, 0xb3, 0x7c, 0x7a, 0x58, - 0xf7, 0x23, 0xfc, 0x90, 0xc3, 0x38, 0x4e, 0xdb, 0xdf, 0xba, 0xb5, 0x02, 0x63, 0x51, 0x59, 0x88, - 0x65, 0xeb, 0xa9, 0xf4, 0xe0, 0xf6, 0x19, 0xad, 0x88, 0xf1, 0x50, 0xbf, 0x9d, 0x4b, 0x46, 0xa4, - 0x3f, 0xf2, 0xf9, 0x69, 0x7f, 0xda, 0x12, 0xfd, 0x94, 0x8f, 0xc8, 0x5a, 0x73, 0x18, 0x9f, 0xf2, - 0x11, 0x59, 0x35, 0xf6, 0xf7, 0x29, 0xbf, 0x98, 0xcf, 0x0a, 0xe8, 0x7f, 0xe4, 0x1f, 0xf4, 0x59, - 0x59, 0xc8, 0xbc, 0x65, 0xe2, 0xd3, 0x9e, 0xce, 0x8a, 0xa0, 0x9f, 0xc1, 0x33, 0xc1, 0x67, 0x7f, - 0x63, 0x3c, 0x55, 0x58, 0x1f, 0x11, 0x45, 0x3e, 0x1e, 0xc2, 0xfa, 0x88, 0x0c, 0x95, 0x8f, 0x9e, - 0xb0, 0x7e, 0x33, 0xbf, 0xdb, 0x2c, 0x12, 0x27, 0xc2, 0x4b, 0x08, 0xef, 0xcb, 0xf9, 0x64, 0x76, - 0x93, 0x23, 0x17, 0xd3, 0x1c, 0x94, 0x44, 0x9e, 0x95, 0x4c, 0xe1, 0x70, 0x7c, 0x96, 0x45, 0x23, - 0xbe, 0xe3, 0x06, 0x88, 0x8b, 0x9c, 0xdd, 0x89, 0x84, 0xd3, 0xaa, 0xdf, 0xcd, 0xc5, 0x52, 0x81, - 0x1c, 0xc9, 0x11, 0xc2, 0xbe, 0x96, 0x24, 0xf2, 0xb6, 0x7f, 0x98, 0x59, 0x8c, 0x85, 0x62, 0x0f, - 0xbe, 0xa7, 0x42, 0x3d, 0xdd, 0x6c, 0xc5, 0xcb, 0x8b, 0xf8, 0x03, 0xdf, 0xcc, 0xc3, 0xa9, 0x04, - 0x29, 0xb9, 0x1c, 0x89, 0xf8, 0x83, 0xc7, 0x92, 0x31, 0x47, 0x75, 0x1e, 0xfb, 0x67, 0x0f, 0x27, - 0xa9, 0x97, 0xa1, 0x58, 0xd1, 0x37, 0xf9, 0xb7, 0xf5, 0x73, 0x96, 0x86, 0xbe, 0x29, 0x9f, 0xb8, - 0x21, 0x9e, 0xac, 0xc2, 0x59, 0x7e, 0x1f, 0x62, 0xda, 0xd6, 0x8a, 0xd9, 0xa6, 0x55, 0x6b, 0xd1, - 0x6c, 0xb5, 0x4c, 0x57, 0x5c, 0xea, 0xbd, 0xb0, 0xbd, 0x35, 0x79, 0xc5, 0xb3, 0x3d, 0xbd, 0xd5, - 0xa0, 0x3e, 0x59, 0xc3, 0x33, 0xdb, 0xb4, 0x61, 0x5a, 0x8d, 0x36, 0x52, 0x4a, 0x2c, 0xd3, 0x59, - 0x91, 0x2a, 0x8f, 0xba, 0x5f, 0x6f, 0xea, 0x96, 0x45, 0x8d, 0xaa, 0x35, 0xbd, 0xe9, 0x51, 0x7e, - 0x19, 0x58, 0xe0, 0x47, 0x82, 0xfc, 0x1d, 0x3a, 0x47, 0x33, 0xc6, 0xab, 0x8c, 0x40, 0x4b, 0x29, - 0xa4, 0xfe, 0x56, 0x31, 0x25, 0x0b, 0xcc, 0x31, 0x52, 0x1f, 0xbf, 0xa7, 0x8b, 0x3b, 0xf4, 0xf4, - 0x35, 0x18, 0x10, 0x61, 0x8d, 0xc5, 0x05, 0x03, 0x3a, 0xce, 0x3f, 0xe4, 0x20, 0xf9, 0x86, 0x46, - 0x50, 0x91, 0x16, 0x5c, 0x58, 0x61, 0xdd, 0x94, 0xde, 0x99, 0xa5, 0x7d, 0x74, 0x66, 0x0f, 0x7e, - 0xe4, 0x3d, 0x38, 0x8f, 0xd8, 0x94, 0x6e, 0x1d, 0xc0, 0xaa, 0x30, 0x94, 0x16, 0xaf, 0x2a, 0xbd, - 0x73, 0xb3, 0xca, 0x93, 0xcf, 0xc2, 0x48, 0x30, 0x40, 0x4c, 0xea, 0x8a, 0x9b, 0x8b, 0x1e, 0xe3, - 0x8c, 0xc7, 0xa9, 0x63, 0x60, 0x74, 0x57, 0x8b, 0xc6, 0x3a, 0x8b, 0xf0, 0x52, 0xff, 0x87, 0x5c, - 0xaf, 0x6c, 0x34, 0x47, 0x3e, 0x2b, 0xbf, 0x0d, 0x03, 0x06, 0xff, 0x28, 0xa1, 0x53, 0xbd, 0xf3, - 0xd5, 0x70, 0x52, 0xcd, 0x2f, 0xa3, 0xfe, 0xd3, 0x5c, 0xcf, 0x24, 0x38, 0xc7, 0xfd, 0xf3, 0xbe, - 0x5c, 0xc8, 0xf8, 0x3c, 0x31, 0x89, 0x5e, 0x85, 0x09, 0x33, 0x8c, 0xd2, 0xdf, 0x08, 0x43, 0x5d, - 0x69, 0xe3, 0x12, 0x1c, 0x47, 0xd7, 0x0d, 0x08, 0x1c, 0xb6, 0x1c, 0xdf, 0x1b, 0xcd, 0x6d, 0x74, - 0x1d, 0x93, 0x8f, 0x4b, 0xed, 0x8c, 0x1b, 0x73, 0x55, 0x73, 0xef, 0x38, 0x26, 0xab, 0x40, 0xf7, - 0xd6, 0xa9, 0xa5, 0x37, 0x36, 0x6c, 0xe7, 0x01, 0x06, 0x43, 0xe5, 0x83, 0x53, 0x1b, 0xe7, 0xf0, - 0x7b, 0x3e, 0x98, 0x3c, 0x0b, 0xa3, 0x6b, 0xad, 0x2e, 0x0d, 0xc2, 0x4f, 0xf2, 0xbb, 0x3e, 0x6d, - 0x84, 0x01, 0x83, 0x1b, 0x92, 0x4b, 0x00, 0x48, 0xe4, 0x61, 0x8a, 0x22, 0xbc, 0xd8, 0xd3, 0x86, - 0x18, 0x64, 0x45, 0x74, 0xd7, 0x05, 0xae, 0xd5, 0x5c, 0x48, 0x8d, 0x96, 0x6d, 0xad, 0x35, 0x3c, - 0xea, 0xb4, 0xb1, 0xa1, 0xe8, 0xcc, 0xa0, 0x9d, 0x43, 0x0a, 0xbc, 0x3a, 0x71, 0x17, 0x6c, 0x6b, - 0x6d, 0x85, 0x3a, 0x6d, 0xd6, 0xd4, 0x17, 0x80, 0x88, 0xa6, 0x3a, 0x78, 0xe8, 0xc1, 0x3f, 0x0e, - 0xbd, 0x19, 0x34, 0xf1, 0x11, 0xfc, 0x34, 0x04, 0x3f, 0x6c, 0x12, 0x86, 0x79, 0x0c, 0x3e, 0x2e, - 0x34, 0x74, 0x61, 0xd0, 0x80, 0x83, 0x50, 0x5e, 0xe7, 0x40, 0x78, 0x57, 0x70, 0x0f, 0x72, 0x4d, - 0xfc, 0x52, 0xbf, 0x58, 0x48, 0xcb, 0xdb, 0x73, 0x20, 0x45, 0x0b, 0xa7, 0xd5, 0xfc, 0x9e, 0xa6, - 0xd5, 0x71, 0xab, 0xdb, 0x6e, 0xe8, 0x9d, 0x4e, 0xe3, 0xbe, 0xd9, 0xc2, 0x27, 0x5c, 0xb8, 0xf0, - 0x69, 0xa3, 0x56, 0xb7, 0x5d, 0xee, 0x74, 0xe6, 0x38, 0x90, 0x3c, 0x0f, 0xa7, 0x18, 0x1d, 0x76, - 0x52, 0x40, 0x59, 0x44, 0x4a, 0xc6, 0x00, 0x83, 0xd8, 0xfa, 0xb4, 0x4f, 0xc0, 0xa0, 0xe0, 0xc9, - 0xd7, 0xaa, 0x7e, 0x6d, 0x80, 0x33, 0x73, 0x59, 0xcf, 0x05, 0x6c, 0xf8, 0xe4, 0xda, 0xaf, 0x0d, - 0xf9, 0xe5, 0x31, 0x54, 0xb3, 0xd5, 0x6d, 0xf3, 0xe8, 0x5b, 0x03, 0x88, 0x0c, 0x7e, 0x93, 0xcb, - 0x30, 0xc6, 0xb8, 0x04, 0x02, 0xe3, 0xd1, 0x6d, 0xfb, 0xb5, 0x18, 0x94, 0x5c, 0x87, 0x33, 0x11, - 0x08, 0xb7, 0x41, 0xf9, 0x93, 0x84, 0x7e, 0x2d, 0x15, 0xa7, 0x7e, 0xa3, 0x10, 0xcd, 0x26, 0x74, - 0x04, 0x1d, 0x71, 0x1e, 0x06, 0x6c, 0x67, 0xad, 0xd1, 0x75, 0x5a, 0x62, 0xec, 0x95, 0x6c, 0x67, - 0xed, 0x8e, 0xd3, 0x22, 0x67, 0xa1, 0xc4, 0x7a, 0xc7, 0x34, 0xc4, 0x10, 0xeb, 0xd7, 0x3b, 0x9d, - 0xaa, 0x41, 0xca, 0xbc, 0x43, 0x30, 0x32, 0x6a, 0xa3, 0x89, 0x5b, 0x7b, 0xee, 0x94, 0xd0, 0xcf, - 0x57, 0xbc, 0x04, 0x12, 0xfb, 0x09, 0xe3, 0xa5, 0xf2, 0x83, 0x80, 0x18, 0x0b, 0x03, 0xb7, 0x25, - 0x06, 0xef, 0x93, 0x38, 0x0b, 0x81, 0x0c, 0x59, 0xf0, 0x4d, 0x8c, 0x41, 0x2a, 0x40, 0x42, 0xaa, - 0xb6, 0x6d, 0x98, 0xf7, 0x4d, 0xca, 0x5f, 0x90, 0xf4, 0xf3, 0x8b, 0xdf, 0x24, 0x56, 0x9b, 0xf0, - 0x99, 0x2c, 0x0a, 0x08, 0x79, 0x93, 0x2b, 0x21, 0xa7, 0xc3, 0xb5, 0x8f, 0xf7, 0x2d, 0xb7, 0xd3, - 0x62, 0x28, 0xd4, 0x4c, 0x2c, 0x8f, 0x0b, 0xa1, 0xfa, 0xdf, 0xf5, 0x27, 0x53, 0x4a, 0x1d, 0x89, - 0x5d, 0x33, 0x0f, 0x20, 0x32, 0xc6, 0x85, 0x97, 0x6b, 0x81, 0x77, 0x7b, 0x88, 0xc9, 0xe0, 0x21, - 0x95, 0x25, 0x57, 0x61, 0x90, 0x7f, 0x51, 0xb5, 0x22, 0xec, 0x1d, 0x74, 0x11, 0x73, 0x3b, 0xe6, - 0xfd, 0xfb, 0xe8, 0x4f, 0x16, 0xa0, 0xc9, 0x65, 0x18, 0xa8, 0x2c, 0xd5, 0xeb, 0xe5, 0x25, 0xff, - 0xa6, 0x18, 0xdf, 0xb2, 0x18, 0x96, 0xdb, 0x70, 0x75, 0xcb, 0xd5, 0x7c, 0x24, 0x79, 0x16, 0x4a, - 0xd5, 0x1a, 0x92, 0xf1, 0x17, 0x9a, 0xc3, 0xdb, 0x5b, 0x93, 0x03, 0x66, 0x87, 0x53, 0x09, 0x14, - 0xd6, 0x7b, 0xb7, 0x5a, 0x91, 0xdc, 0x25, 0x78, 0xbd, 0x0f, 0x4d, 0x03, 0xaf, 0x9d, 0xb5, 0x00, - 0x4d, 0x5e, 0x81, 0x91, 0x3a, 0x75, 0x4c, 0xbd, 0xb5, 0xd4, 0xc5, 0xad, 0xa2, 0x14, 0xf1, 0xd1, - 0x45, 0x78, 0xc3, 0x42, 0x84, 0x16, 0x21, 0x23, 0x17, 0xa1, 0x38, 0x6f, 0x5a, 0xfe, 0x73, 0x09, - 0xf4, 0xa7, 0x5f, 0x37, 0x2d, 0x4f, 0x43, 0x28, 0x79, 0x16, 0x0a, 0xb7, 0x56, 0xaa, 0xc2, 0x13, - 0x0c, 0x79, 0xbd, 0xef, 0x45, 0xa2, 0x47, 0xde, 0x5a, 0xa9, 0x92, 0x57, 0x60, 0x88, 0x2d, 0x62, - 0xd4, 0x6a, 0x52, 0x57, 0x19, 0xc6, 0x8f, 0xe1, 0x21, 0x0b, 0x7d, 0xa0, 0xec, 0xd3, 0x11, 0x50, - 0x92, 0xdb, 0x30, 0x11, 0x8f, 0x84, 0x2f, 0x9e, 0xec, 0xa0, 0xc5, 0xb5, 0x21, 0x70, 0x69, 0x41, - 0x33, 0x13, 0x05, 0x89, 0x01, 0x4a, 0x1c, 0xc6, 0xf6, 0x75, 0x68, 0x75, 0xf2, 0x78, 0xcd, 0x57, - 0xb6, 0xb7, 0x26, 0x9f, 0x4b, 0x30, 0x6d, 0x38, 0x82, 0x4a, 0xe2, 0x9e, 0xc9, 0x49, 0xfd, 0x3f, - 0xf3, 0xe9, 0x69, 0xca, 0x8e, 0x60, 0x76, 0xda, 0xe7, 0xc5, 0x77, 0x6c, 0x4c, 0x14, 0x0f, 0x30, - 0x26, 0xee, 0xc3, 0x78, 0xd9, 0x68, 0x9b, 0x56, 0x19, 0x7f, 0xba, 0x8b, 0x73, 0x65, 0x9c, 0xed, - 0xa4, 0xd7, 0x8b, 0x31, 0xb4, 0xf8, 0x1e, 0x1e, 0x4a, 0x99, 0xa1, 0x1a, 0x3a, 0xc7, 0x35, 0xda, - 0xf7, 0xf5, 0x46, 0x93, 0x67, 0xf8, 0xd2, 0xe2, 0x4c, 0xd5, 0x9f, 0xca, 0xef, 0x90, 0x59, 0xed, - 0x71, 0x94, 0xbe, 0xfa, 0x95, 0x7c, 0xef, 0xe4, 0x76, 0x8f, 0xa5, 0x50, 0xfe, 0x28, 0x9f, 0x92, - 0x6a, 0xee, 0x40, 0x92, 0xb8, 0x0a, 0x83, 0x9c, 0x4d, 0xe0, 0x79, 0x8c, 0x13, 0x30, 0x57, 0x56, - 0x9c, 0xf8, 0x7d, 0x34, 0x59, 0x82, 0x33, 0xe5, 0xfb, 0xf7, 0x69, 0xd3, 0x0b, 0x83, 0x6a, 0x2f, - 0x85, 0x31, 0x6a, 0x79, 0x10, 0x61, 0x81, 0x0f, 0x83, 0x72, 0x63, 0x2c, 0x96, 0xd4, 0x72, 0x64, - 0x05, 0xce, 0xc5, 0xe1, 0x75, 0xbe, 0x6b, 0x29, 0x4a, 0x71, 0x85, 0x13, 0x1c, 0xf9, 0x7f, 0x5a, - 0x46, 0xd9, 0xb4, 0x56, 0xe2, 0xea, 0xd2, 0xdf, 0xab, 0x95, 0xb8, 0xd4, 0xa4, 0x96, 0x53, 0xbf, - 0x59, 0x90, 0x33, 0xf2, 0x3d, 0xbe, 0x3e, 0x62, 0x37, 0x22, 0x9e, 0xe1, 0xbb, 0x1d, 0x32, 0xaf, - 0x88, 0x00, 0x2b, 0x46, 0xd7, 0xf1, 0x9d, 0x28, 0x83, 0x00, 0x0f, 0x08, 0x94, 0x97, 0xce, 0x80, - 0x92, 0x54, 0xa1, 0x58, 0x76, 0xd6, 0xb8, 0x45, 0xbe, 0xd3, 0x9b, 0x33, 0xdd, 0x59, 0x73, 0xd3, - 0xdf, 0x9c, 0x31, 0x16, 0xea, 0x9f, 0xcf, 0xf7, 0x48, 0xa2, 0xf7, 0x58, 0x4e, 0x22, 0x3f, 0x97, - 0xcf, 0x4a, 0x87, 0x77, 0x5c, 0xbd, 0xdd, 0x3e, 0x64, 0xe1, 0x1c, 0x6f, 0x57, 0xc0, 0x43, 0x14, - 0xce, 0xef, 0xe7, 0xb3, 0x72, 0xfb, 0x9d, 0x08, 0x67, 0x7f, 0x13, 0x64, 0xaa, 0x48, 0x1f, 0x63, - 0x9b, 0x5b, 0x56, 0x85, 0xfe, 0x7d, 0x7a, 0x7c, 0xa5, 0x89, 0xf4, 0x64, 0x08, 0x1f, 0x48, 0x4b, - 0xff, 0x20, 0x9f, 0x99, 0xc3, 0xf2, 0x44, 0xa6, 0x87, 0x29, 0xd3, 0x93, 0xa1, 0x7f, 0xa0, 0xa1, - 0x9f, 0x2a, 0xd3, 0x93, 0xb1, 0x7f, 0x20, 0x3d, 0xfd, 0xbd, 0x7c, 0x7a, 0x96, 0xd6, 0x23, 0x50, - 0xd2, 0xc3, 0x70, 0xca, 0xf4, 0xbb, 0xa1, 0x78, 0xa0, 0x6e, 0xe8, 0x3f, 0x80, 0x15, 0x95, 0x14, - 0xe8, 0x91, 0x8d, 0xfa, 0xef, 0x55, 0x81, 0x1e, 0xc2, 0x90, 0x7f, 0x9c, 0x05, 0xfa, 0x13, 0x85, - 0x64, 0x66, 0xe2, 0xc7, 0x75, 0x4d, 0x72, 0xf6, 0xb9, 0x26, 0xf9, 0xe5, 0xc8, 0x3b, 0x30, 0x1e, - 0xca, 0x52, 0x0e, 0x8c, 0x86, 0x37, 0x5e, 0x4d, 0x86, 0x6a, 0xbc, 0xcf, 0x70, 0x22, 0x82, 0x4f, - 0x9c, 0x5a, 0xfd, 0x6e, 0x21, 0x99, 0xde, 0xf9, 0xa4, 0x37, 0xf6, 0xd9, 0x1b, 0x77, 0xe0, 0xdc, - 0x4c, 0xd7, 0x71, 0xa8, 0xe5, 0xa5, 0x77, 0x0a, 0x1e, 0xde, 0x37, 0x39, 0x45, 0x23, 0xd9, 0x39, - 0x19, 0x85, 0x19, 0x5b, 0xf1, 0x20, 0x23, 0xce, 0x76, 0x20, 0x64, 0xdb, 0xe5, 0x14, 0x69, 0x6c, - 0xd3, 0x0b, 0xab, 0xbf, 0x93, 0x4f, 0x26, 0xe4, 0x3e, 0xe9, 0xfa, 0xfd, 0x75, 0xbd, 0xfa, 0xc5, - 0x42, 0x3c, 0x29, 0xf9, 0xc9, 0x02, 0xb1, 0xff, 0xee, 0xf0, 0x25, 0x89, 0xe3, 0x46, 0xfa, 0x0a, - 0x1f, 0x9e, 0xf5, 0x15, 0x3e, 0x5e, 0xfd, 0xe5, 0x62, 0x3c, 0xc1, 0xfb, 0x49, 0x77, 0x1c, 0x5d, - 0x77, 0x90, 0x65, 0x38, 0x23, 0xe6, 0x36, 0x1f, 0x84, 0x19, 0x32, 0xc4, 0xfc, 0xc5, 0x13, 0xed, - 0x89, 0x69, 0xb1, 0xeb, 0x52, 0xa7, 0xe1, 0xe9, 0xee, 0x83, 0x06, 0xa6, 0xd4, 0xd0, 0x52, 0x0b, - 0x32, 0x86, 0x62, 0x56, 0x8b, 0x32, 0x1c, 0x0c, 0x19, 0xfa, 0x13, 0x62, 0x82, 0x61, 0x5a, 0x41, - 0xf5, 0xd7, 0x73, 0x30, 0x11, 0xff, 0x1c, 0x32, 0x05, 0x83, 0xec, 0x77, 0x10, 0x29, 0x40, 0xca, - 0x00, 0xce, 0x39, 0x72, 0x2f, 0x02, 0x9f, 0x86, 0xbc, 0x0a, 0x43, 0xe8, 0xb0, 0x81, 0x05, 0xf2, - 0x61, 0x80, 0x86, 0xb0, 0x00, 0xa6, 0xa5, 0xe5, 0xc5, 0x42, 0x52, 0xf2, 0x26, 0x0c, 0x57, 0x43, - 0xcf, 0x34, 0x71, 0xe7, 0x85, 0x0e, 0xb1, 0x52, 0xc9, 0x90, 0x40, 0x93, 0xa9, 0xd5, 0x6f, 0xe7, - 0x43, 0x55, 0x3f, 0x31, 0x4d, 0x0f, 0x64, 0x9a, 0x7e, 0xb5, 0x00, 0xe7, 0xe2, 0xee, 0x0b, 0x27, - 0x07, 0x51, 0x62, 0x1e, 0xf8, 0x53, 0x70, 0x26, 0x2e, 0x9b, 0x0a, 0x93, 0x46, 0x7f, 0xef, 0x6b, - 0xb4, 0xa9, 0xed, 0xad, 0xc9, 0xa7, 0x93, 0x9e, 0x23, 0xac, 0xb2, 0xd4, 0x8b, 0xb5, 0xd4, 0x4a, - 0x52, 0x7b, 0xe6, 0x23, 0xf2, 0xa6, 0xe9, 0x31, 0xef, 0x99, 0x9f, 0xcb, 0x27, 0x7b, 0xe6, 0xe4, - 0x50, 0x4c, 0x4c, 0x28, 0xff, 0x30, 0xe7, 0xdf, 0x0f, 0x2f, 0x98, 0xae, 0x57, 0xb5, 0x1e, 0xea, - 0x2d, 0x33, 0x78, 0x74, 0x4d, 0x6e, 0xfa, 0xe9, 0xd2, 0x19, 0x52, 0x7a, 0xf7, 0x81, 0x1e, 0x5c, - 0x22, 0x5d, 0x7a, 0xcb, 0x74, 0x45, 0x6e, 0xeb, 0xa7, 0x13, 0x09, 0xd3, 0xfd, 0x62, 0xe4, 0xb2, - 0x74, 0xf7, 0x2f, 0xad, 0x51, 0xf2, 0x63, 0x02, 0x71, 0xd7, 0x3f, 0xb2, 0x68, 0xba, 0xae, 0x69, - 0xad, 0xc9, 0x19, 0x61, 0x71, 0xb5, 0x6c, 0x73, 0x78, 0x23, 0x9e, 0xa3, 0x37, 0x52, 0x40, 0xfd, - 0x57, 0x39, 0xb8, 0xc0, 0x38, 0x61, 0x20, 0x93, 0xc4, 0x87, 0x1d, 0xa8, 0xc3, 0xdb, 0x3d, 0x24, - 0x25, 0x34, 0xe0, 0x99, 0xe4, 0xe3, 0xa4, 0x18, 0x61, 0x8c, 0x7b, 0x0f, 0xd9, 0xef, 0xeb, 0xd5, - 0xc6, 0xf3, 0x8b, 0xdc, 0x16, 0xba, 0x6d, 0x5a, 0x06, 0x79, 0x02, 0xce, 0xde, 0xa9, 0xcf, 0x6a, - 0x8d, 0xdb, 0xd5, 0xa5, 0x4a, 0xe3, 0xce, 0x52, 0xbd, 0x36, 0x3b, 0x53, 0x9d, 0xab, 0xce, 0x56, - 0x26, 0xfa, 0xc8, 0x69, 0x18, 0x0f, 0x51, 0xf3, 0x77, 0x16, 0xcb, 0x4b, 0x13, 0x39, 0x72, 0x0a, - 0x46, 0x43, 0xe0, 0xf4, 0xf2, 0xca, 0x44, 0xfe, 0xf9, 0x8f, 0xc3, 0x30, 0xba, 0x6c, 0x73, 0x7f, - 0x2d, 0x32, 0x02, 0x83, 0xcb, 0xd3, 0xf5, 0x59, 0xed, 0x2e, 0x32, 0x01, 0x28, 0x55, 0x66, 0x97, - 0x18, 0xc3, 0xdc, 0xf3, 0xff, 0x4f, 0x0e, 0xa0, 0x3e, 0xb7, 0x52, 0x13, 0x84, 0xc3, 0x30, 0x50, - 0x5d, 0xba, 0x5b, 0x5e, 0xa8, 0x32, 0xba, 0x41, 0x28, 0x2e, 0xd7, 0x66, 0x59, 0x0d, 0x43, 0xd0, - 0x3f, 0xb3, 0xb0, 0x5c, 0x9f, 0x9d, 0xc8, 0x33, 0xa0, 0x36, 0x5b, 0xae, 0x4c, 0x14, 0x18, 0xf0, - 0x9e, 0x56, 0x5d, 0x99, 0x9d, 0x28, 0xb2, 0x3f, 0x17, 0xea, 0x2b, 0xe5, 0x95, 0x89, 0x7e, 0xf6, - 0xe7, 0x1c, 0xfe, 0x59, 0x62, 0xcc, 0xea, 0xb3, 0x2b, 0xf8, 0x63, 0x80, 0x35, 0x61, 0xce, 0xff, - 0x35, 0xc8, 0x50, 0x8c, 0x75, 0xa5, 0xaa, 0x4d, 0x0c, 0xb1, 0x1f, 0x8c, 0x25, 0xfb, 0x01, 0xac, - 0x71, 0xda, 0xec, 0xe2, 0xf2, 0xdd, 0xd9, 0x89, 0x61, 0xc6, 0x6b, 0xf1, 0x36, 0x03, 0x8f, 0xb0, - 0x3f, 0xb5, 0x45, 0xf6, 0xe7, 0x28, 0xe3, 0xa4, 0xcd, 0x96, 0x17, 0x6a, 0xe5, 0x95, 0xf9, 0x89, - 0x31, 0xd6, 0x1e, 0xe4, 0x39, 0xce, 0x4b, 0x2e, 0x95, 0x17, 0x67, 0x27, 0x26, 0x04, 0x4d, 0x65, - 0xa1, 0xba, 0x74, 0x7b, 0xe2, 0x14, 0x36, 0xe4, 0xbd, 0x45, 0xfc, 0x41, 0x58, 0x01, 0xfc, 0xeb, - 0xf4, 0xf3, 0x3f, 0x00, 0xa5, 0xe5, 0x3a, 0x9a, 0x49, 0xe7, 0xe1, 0xf4, 0x72, 0xbd, 0xb1, 0xf2, - 0x5e, 0x6d, 0x36, 0x26, 0xef, 0x53, 0x30, 0xea, 0x23, 0x16, 0xaa, 0x4b, 0x77, 0x3e, 0xc3, 0xa5, - 0xed, 0x83, 0x16, 0xcb, 0x33, 0xcb, 0xf5, 0x89, 0x3c, 0xeb, 0x15, 0x1f, 0x74, 0xaf, 0xba, 0x54, - 0x59, 0xbe, 0x57, 0x9f, 0x28, 0x3c, 0xff, 0x10, 0x46, 0x78, 0xee, 0xe9, 0x65, 0xc7, 0x5c, 0x33, - 0x2d, 0x72, 0x09, 0x9e, 0xa8, 0xcc, 0xde, 0xad, 0xce, 0xcc, 0x36, 0x96, 0xb5, 0xea, 0xcd, 0xea, - 0x52, 0xac, 0xa6, 0xb3, 0x70, 0x2a, 0x8a, 0x2e, 0xd7, 0xaa, 0x13, 0x39, 0x72, 0x0e, 0x48, 0x14, - 0x7c, 0xab, 0xbc, 0x38, 0x37, 0x91, 0x27, 0x0a, 0x9c, 0x89, 0xc2, 0xab, 0x4b, 0x2b, 0x77, 0x96, - 0x66, 0x27, 0x0a, 0xcf, 0xff, 0x95, 0x1c, 0x9c, 0x4d, 0xcd, 0x4f, 0x40, 0x54, 0x78, 0x6a, 0x76, - 0xa1, 0x5c, 0x5f, 0xa9, 0xce, 0xd4, 0x67, 0xcb, 0xda, 0xcc, 0x7c, 0x63, 0xa6, 0xbc, 0x32, 0x7b, - 0x73, 0x59, 0x7b, 0xaf, 0x71, 0x73, 0x76, 0x69, 0x56, 0x2b, 0x2f, 0x4c, 0xf4, 0x91, 0x67, 0x61, - 0x32, 0x83, 0xa6, 0x3e, 0x3b, 0x73, 0x47, 0xab, 0xae, 0xbc, 0x37, 0x91, 0x23, 0xcf, 0xc0, 0xa5, - 0x4c, 0x22, 0xf6, 0x7b, 0x22, 0x4f, 0x9e, 0x82, 0x0b, 0x59, 0x24, 0xef, 0x2e, 0x4c, 0x14, 0x9e, - 0xff, 0xd9, 0x1c, 0x90, 0x64, 0x80, 0x79, 0xf2, 0x34, 0x5c, 0x64, 0x7a, 0xd1, 0xc8, 0x6e, 0xe0, - 0x33, 0x70, 0x29, 0x95, 0x42, 0x6a, 0xde, 0x24, 0x3c, 0x99, 0x41, 0x22, 0x1a, 0x77, 0x11, 0x94, - 0x74, 0x02, 0x6c, 0xda, 0xaf, 0xe5, 0xe0, 0x6c, 0xaa, 0x8b, 0x24, 0xb9, 0x02, 0xcf, 0x95, 0x2b, - 0x8b, 0xac, 0x6f, 0x66, 0x56, 0xaa, 0xcb, 0x4b, 0xf5, 0xc6, 0xe2, 0x5c, 0xb9, 0xc1, 0xb4, 0xef, - 0x4e, 0x3d, 0xd6, 0x9b, 0x97, 0x41, 0xed, 0x41, 0x39, 0x33, 0x5f, 0x5e, 0xba, 0xc9, 0x86, 0x1f, - 0x79, 0x0e, 0x9e, 0xce, 0xa4, 0x9b, 0x5d, 0x2a, 0x4f, 0x2f, 0xcc, 0x56, 0x26, 0xf2, 0xe4, 0x63, - 0xf0, 0x4c, 0x26, 0x55, 0xa5, 0x5a, 0xe7, 0x64, 0x85, 0xe9, 0xca, 0xb7, 0xff, 0xa7, 0xa7, 0xfa, - 0xbe, 0xfd, 0x87, 0x4f, 0xe5, 0x7e, 0xff, 0x0f, 0x9f, 0xca, 0xfd, 0xb3, 0x3f, 0x7c, 0x2a, 0xf7, - 0xd9, 0xeb, 0x7b, 0x49, 0x1c, 0xc0, 0xa7, 0xa9, 0xd5, 0x12, 0xae, 0xe6, 0x2f, 0xff, 0xff, 0x01, - 0x00, 0x00, 0xff, 0xff, 0x7b, 0x68, 0x21, 0xe3, 0xaa, 0x8a, 0x01, 0x00, + // 17381 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x6b, 0x78, 0x25, 0xc9, + 0x75, 0x18, 0x86, 0xfb, 0xc0, 0x05, 0x70, 0xf0, 0x9c, 0x9a, 0x57, 0xcf, 0xec, 0xec, 0x62, 0xb7, + 0x97, 0x1c, 0xce, 0x2c, 0x77, 0x31, 0xdc, 0xd9, 0xd9, 0x5d, 0xee, 0x8b, 0xcb, 0x0b, 0x5c, 0x60, + 0x70, 0x67, 0xf0, 0xb8, 0xdb, 0x17, 0x33, 0xc3, 0xa5, 0x44, 0x5e, 0x35, 0x6e, 0xd7, 0x00, 0xbd, + 0x73, 0x6f, 0xf7, 0x55, 0x77, 0xdf, 0xc1, 0x60, 0x9d, 0x87, 0xe8, 0xc8, 0xb2, 0x64, 0x51, 0x34, + 0x43, 0x45, 0xa2, 0x2c, 0x3b, 0x09, 0x65, 0xc7, 0x89, 0x2c, 0xcb, 0x62, 0xe4, 0x28, 0x92, 0x28, + 0x89, 0xb1, 0x64, 0x3a, 0x0e, 0x2d, 0x7d, 0xd6, 0x27, 0x39, 0x89, 0xe3, 0x2f, 0x71, 0x40, 0x47, + 0x89, 0xff, 0xe0, 0x4b, 0xf2, 0x29, 0x89, 0xbe, 0x98, 0x51, 0xec, 0x7c, 0xf9, 0xea, 0x54, 0x75, + 0x77, 0xf5, 0xeb, 0xe2, 0xb9, 0xc6, 0x82, 0x83, 0x3f, 0x33, 0xb8, 0xe7, 0x9c, 0x3a, 0x55, 0x7d, + 0xea, 0x54, 0xd5, 0xa9, 0xaa, 0x53, 0xe7, 0xc0, 0x55, 0x8f, 0xb6, 0x68, 0xc7, 0x76, 0xbc, 0x6b, + 0x2d, 0xba, 0xa6, 0x37, 0x37, 0xaf, 0x79, 0x9b, 0x1d, 0xea, 0x5e, 0xa3, 0x0f, 0xa9, 0xe5, 0xf9, + 0xff, 0x4d, 0x75, 0x1c, 0xdb, 0xb3, 0x49, 0x89, 0xff, 0xba, 0x78, 0x66, 0xcd, 0x5e, 0xb3, 0x11, + 0x74, 0x8d, 0xfd, 0xc5, 0xb1, 0x17, 0x2f, 0xad, 0xd9, 0xf6, 0x5a, 0x8b, 0x5e, 0xc3, 0x5f, 0xab, + 0xdd, 0xfb, 0xd7, 0x5c, 0xcf, 0xe9, 0x36, 0x3d, 0x81, 0x9d, 0x8c, 0x63, 0x3d, 0xb3, 0x4d, 0x5d, + 0x4f, 0x6f, 0x77, 0x04, 0xc1, 0x53, 0x71, 0x82, 0x0d, 0x47, 0xef, 0x74, 0xa8, 0x23, 0x2a, 0xbf, + 0xf8, 0xb1, 0xa0, 0x9d, 0x7a, 0xb3, 0x49, 0x5d, 0xb7, 0x65, 0xba, 0xde, 0xb5, 0x87, 0x2f, 0x4a, + 0xbf, 0x04, 0xe1, 0x33, 0xe9, 0x1f, 0x84, 0xff, 0x0a, 0x92, 0x17, 0xd2, 0x49, 0xfc, 0x1a, 0x63, + 0x55, 0xab, 0x5f, 0xc9, 0xc3, 0xe0, 0x22, 0xf5, 0x74, 0x43, 0xf7, 0x74, 0x72, 0x09, 0xfa, 0xab, + 0x96, 0x41, 0x1f, 0x29, 0xb9, 0xa7, 0x73, 0x57, 0x0a, 0xd3, 0xa5, 0xed, 0xad, 0xc9, 0x3c, 0x35, + 0x35, 0x0e, 0x24, 0x4f, 0x42, 0x71, 0x65, 0xb3, 0x43, 0x95, 0xfc, 0xd3, 0xb9, 0x2b, 0x43, 0xd3, + 0x43, 0xdb, 0x5b, 0x93, 0xfd, 0x28, 0x34, 0x0d, 0xc1, 0xe4, 0x19, 0xc8, 0x57, 0x2b, 0x4a, 0x01, + 0x91, 0xa7, 0xb6, 0xb7, 0x26, 0x47, 0xbb, 0xa6, 0xf1, 0xbc, 0xdd, 0x36, 0x3d, 0xda, 0xee, 0x78, + 0x9b, 0x5a, 0xbe, 0x5a, 0x21, 0x97, 0xa1, 0x38, 0x63, 0x1b, 0x54, 0x29, 0x22, 0x11, 0xd9, 0xde, + 0x9a, 0x1c, 0x6b, 0xda, 0x06, 0x95, 0xa8, 0x10, 0x4f, 0x3e, 0x0d, 0xc5, 0x15, 0xb3, 0x4d, 0x95, + 0xfe, 0xa7, 0x73, 0x57, 0x86, 0xaf, 0x5f, 0x9c, 0xe2, 0xe2, 0x9b, 0xf2, 0xc5, 0x37, 0xb5, 0xe2, + 0xcb, 0x77, 0x7a, 0xe2, 0xdb, 0x5b, 0x93, 0x7d, 0xdb, 0x5b, 0x93, 0x45, 0x26, 0xf2, 0x2f, 0x7f, + 0x67, 0x32, 0xa7, 0x61, 0x49, 0xf2, 0x26, 0x0c, 0xcf, 0xb4, 0xba, 0xae, 0x47, 0x9d, 0x25, 0xbd, + 0x4d, 0x95, 0x12, 0x56, 0x78, 0x71, 0x7b, 0x6b, 0xf2, 0x5c, 0x93, 0x83, 0x1b, 0x96, 0xde, 0x96, + 0x2b, 0x96, 0xc9, 0xd5, 0x5f, 0xcf, 0xc1, 0x78, 0x9d, 0xba, 0xae, 0x69, 0x5b, 0x81, 0x6c, 0x3e, + 0x0a, 0x43, 0x02, 0x54, 0xad, 0xa0, 0x7c, 0x86, 0xa6, 0x07, 0xb6, 0xb7, 0x26, 0x0b, 0xae, 0x69, + 0x68, 0x21, 0x86, 0x7c, 0x02, 0x06, 0xee, 0x99, 0xde, 0xfa, 0xe2, 0x5c, 0x59, 0xc8, 0xe9, 0xdc, + 0xf6, 0xd6, 0x24, 0xd9, 0x30, 0xbd, 0xf5, 0x46, 0xfb, 0xbe, 0x2e, 0x55, 0xe8, 0x93, 0x91, 0x05, + 0x98, 0xa8, 0x39, 0xe6, 0x43, 0xdd, 0xa3, 0xb7, 0xe9, 0x66, 0xcd, 0x6e, 0x99, 0xcd, 0x4d, 0x21, + 0xc5, 0xa7, 0xb7, 0xb7, 0x26, 0x2f, 0x75, 0x38, 0xae, 0xf1, 0x80, 0x6e, 0x36, 0x3a, 0x88, 0x95, + 0x98, 0x24, 0x4a, 0xaa, 0xbf, 0x51, 0x82, 0x91, 0x3b, 0x2e, 0x75, 0x82, 0x76, 0x5f, 0x86, 0x22, + 0xfb, 0x2d, 0x9a, 0x8c, 0x32, 0xef, 0xba, 0xd4, 0x91, 0x65, 0xce, 0xf0, 0xe4, 0x2a, 0xf4, 0x2f, + 0xd8, 0x6b, 0xa6, 0x25, 0x9a, 0x7d, 0x7a, 0x7b, 0x6b, 0x72, 0xbc, 0xc5, 0x00, 0x12, 0x25, 0xa7, + 0x20, 0x9f, 0x82, 0x91, 0x6a, 0x9b, 0xe9, 0x90, 0x6d, 0xe9, 0x9e, 0xed, 0x88, 0xd6, 0xa2, 0x74, + 0x4d, 0x09, 0x2e, 0x15, 0x8c, 0xd0, 0x93, 0xd7, 0x01, 0xca, 0xf7, 0xea, 0x9a, 0xdd, 0xa2, 0x65, + 0x6d, 0x49, 0x28, 0x03, 0x96, 0xd6, 0x37, 0xdc, 0x86, 0x63, 0xb7, 0x68, 0x43, 0x77, 0xe4, 0x6a, + 0x25, 0x6a, 0x32, 0x0b, 0x63, 0x65, 0x1c, 0x15, 0x1a, 0xfd, 0xc1, 0x2e, 0x75, 0x3d, 0x57, 0xe9, + 0x7f, 0xba, 0x70, 0x65, 0x68, 0xfa, 0xc9, 0xed, 0xad, 0xc9, 0x0b, 0x7c, 0xbc, 0x34, 0x1c, 0x81, + 0x92, 0x58, 0xc4, 0x0a, 0x91, 0x69, 0x18, 0x2d, 0xbf, 0xdf, 0x75, 0x68, 0xd5, 0xa0, 0x96, 0x67, + 0x7a, 0x9b, 0x42, 0x43, 0x2e, 0x6d, 0x6f, 0x4d, 0x2a, 0x3a, 0x43, 0x34, 0x4c, 0x81, 0x91, 0x98, + 0x44, 0x8b, 0x90, 0x65, 0x38, 0x75, 0x73, 0xa6, 0x56, 0xa7, 0xce, 0x43, 0xb3, 0x49, 0xcb, 0xcd, + 0xa6, 0xdd, 0xb5, 0x3c, 0x65, 0x00, 0xf9, 0x3c, 0xb3, 0xbd, 0x35, 0xf9, 0xe4, 0x5a, 0xb3, 0xd3, + 0x70, 0x39, 0xb6, 0xa1, 0x73, 0xb4, 0xc4, 0x2c, 0x59, 0x96, 0x7c, 0x16, 0x46, 0x57, 0x1c, 0xa6, + 0x85, 0x46, 0x85, 0x32, 0xb8, 0x32, 0x88, 0xfa, 0x7f, 0x6e, 0x4a, 0xcc, 0x54, 0x1c, 0xea, 0xf7, + 0x2c, 0x6f, 0xac, 0xc7, 0x0b, 0x34, 0x0c, 0xc4, 0xc9, 0x8d, 0x8d, 0xb0, 0x22, 0x14, 0x14, 0xf6, + 0xf1, 0xa6, 0x43, 0x8d, 0x84, 0xb6, 0x0d, 0x61, 0x9b, 0xaf, 0x6e, 0x6f, 0x4d, 0x7e, 0xd4, 0x11, + 0x34, 0x8d, 0x9e, 0x6a, 0x97, 0xc9, 0x8a, 0xcc, 0xc2, 0x20, 0xd3, 0xa6, 0xdb, 0xa6, 0x65, 0x28, + 0xf0, 0x74, 0xee, 0xca, 0xd8, 0xf5, 0x09, 0xbf, 0xf5, 0x3e, 0x7c, 0xfa, 0xfc, 0xf6, 0xd6, 0xe4, + 0x69, 0xa6, 0x83, 0x8d, 0x07, 0xa6, 0x25, 0x4f, 0x11, 0x41, 0x51, 0x36, 0x8a, 0xa6, 0x6d, 0x0f, + 0x87, 0xee, 0x70, 0x38, 0x8a, 0x56, 0x6d, 0x2f, 0x3e, 0x6c, 0x7d, 0x32, 0x32, 0x03, 0xa3, 0xd3, + 0xb6, 0x57, 0xb5, 0x5c, 0x4f, 0xb7, 0x9a, 0xb4, 0x5a, 0x51, 0x46, 0xb0, 0x1c, 0xaa, 0x05, 0x2b, + 0x67, 0x0a, 0x4c, 0x23, 0x32, 0x29, 0x45, 0xcb, 0xa8, 0xff, 0xa2, 0x08, 0x63, 0xac, 0x4f, 0xa4, + 0xe1, 0x53, 0x66, 0x33, 0x01, 0x83, 0xb0, 0x5a, 0xdc, 0x8e, 0xde, 0xa4, 0x62, 0x24, 0xe1, 0x57, + 0x58, 0x3e, 0x50, 0xe2, 0x19, 0xa7, 0x27, 0x57, 0x61, 0x90, 0x83, 0xaa, 0x15, 0x31, 0xb8, 0x46, + 0xb7, 0xb7, 0x26, 0x87, 0x5c, 0x84, 0x35, 0x4c, 0x43, 0x0b, 0xd0, 0x4c, 0xbb, 0xf9, 0xdf, 0xf3, + 0xb6, 0xeb, 0x31, 0xe6, 0x62, 0x6c, 0xe1, 0x67, 0x88, 0x02, 0xeb, 0x02, 0x25, 0x6b, 0x77, 0xb4, + 0x10, 0x79, 0x0d, 0x80, 0x43, 0xca, 0x86, 0xe1, 0x88, 0x01, 0x76, 0x61, 0x7b, 0x6b, 0xf2, 0xac, + 0x60, 0xa1, 0x1b, 0x86, 0x3c, 0x3a, 0x25, 0x62, 0xd2, 0x86, 0x11, 0xfe, 0x6b, 0x41, 0x5f, 0xa5, + 0x2d, 0x3e, 0xba, 0x86, 0xaf, 0x5f, 0xf1, 0x3b, 0x31, 0x2a, 0x9d, 0x29, 0x99, 0x74, 0xd6, 0xf2, + 0x9c, 0xcd, 0xe9, 0x49, 0x31, 0x21, 0x9f, 0x17, 0x55, 0xb5, 0x10, 0x27, 0x4f, 0x05, 0x72, 0x19, + 0x36, 0x4f, 0xcf, 0xd9, 0xce, 0x86, 0xee, 0x18, 0xd4, 0x98, 0xde, 0x94, 0xe7, 0xe9, 0xfb, 0x3e, + 0xb8, 0xb1, 0x2a, 0xab, 0x9e, 0x4c, 0xce, 0x3a, 0x9d, 0x73, 0xab, 0x77, 0x57, 0x51, 0xe5, 0x06, + 0x12, 0xd2, 0x72, 0xbb, 0xab, 0x71, 0x35, 0x8b, 0x96, 0x61, 0x53, 0x01, 0x07, 0xdc, 0xa5, 0x0e, + 0x9b, 0xc4, 0x71, 0xd4, 0x89, 0xa9, 0x40, 0x30, 0x79, 0xc8, 0x31, 0x49, 0x1e, 0xa2, 0xc8, 0xc5, + 0xb7, 0xe1, 0x54, 0x42, 0x14, 0x64, 0x02, 0x0a, 0x0f, 0xe8, 0x26, 0x57, 0x17, 0x8d, 0xfd, 0x49, + 0xce, 0x40, 0xff, 0x43, 0xbd, 0xd5, 0x15, 0x4b, 0xa8, 0xc6, 0x7f, 0xbc, 0x9e, 0xff, 0x64, 0x8e, + 0xad, 0x38, 0x64, 0xc6, 0xb6, 0x2c, 0xda, 0xf4, 0xe4, 0x45, 0xe7, 0x15, 0x18, 0x5a, 0xb0, 0x9b, + 0x7a, 0x0b, 0xfb, 0x91, 0xeb, 0x9d, 0xb2, 0xbd, 0x35, 0x79, 0x86, 0x75, 0xe0, 0x54, 0x8b, 0x61, + 0xa4, 0x36, 0x85, 0xa4, 0x4c, 0x01, 0x34, 0xda, 0xb6, 0x3d, 0x8a, 0x05, 0xf3, 0xa1, 0x02, 0x60, + 0x41, 0x07, 0x51, 0xb2, 0x02, 0x84, 0xc4, 0xe4, 0x1a, 0x0c, 0xd6, 0xd8, 0x3a, 0xdb, 0xb4, 0x5b, + 0x42, 0xf9, 0x70, 0x29, 0xc0, 0xb5, 0x57, 0x1e, 0xab, 0x3e, 0x91, 0x3a, 0x0f, 0x63, 0x33, 0x2d, + 0x93, 0x5a, 0x9e, 0xdc, 0x6a, 0x36, 0x92, 0xcb, 0x6b, 0xd4, 0xf2, 0xe4, 0x56, 0xe3, 0x98, 0xd7, + 0x19, 0x54, 0x6e, 0x75, 0x40, 0xaa, 0xfe, 0x7e, 0x01, 0x2e, 0xdc, 0xee, 0xae, 0x52, 0xc7, 0xa2, + 0x1e, 0x75, 0xc5, 0x82, 0x1c, 0x70, 0x5d, 0x82, 0x53, 0x09, 0xa4, 0xe0, 0x8e, 0x0b, 0xe5, 0x83, + 0x00, 0xd9, 0x10, 0x6b, 0xbc, 0x3c, 0xdb, 0x26, 0x8a, 0x92, 0x79, 0x18, 0x0f, 0x81, 0xac, 0x11, + 0xae, 0x92, 0xc7, 0xa5, 0xe4, 0xa9, 0xed, 0xad, 0xc9, 0x8b, 0x12, 0x37, 0xd6, 0x6c, 0x59, 0x83, + 0xe3, 0xc5, 0xc8, 0x6d, 0x98, 0x08, 0x41, 0x37, 0x1d, 0xbb, 0xdb, 0x71, 0x95, 0x02, 0xb2, 0x9a, + 0xdc, 0xde, 0x9a, 0x7c, 0x42, 0x62, 0xb5, 0x86, 0x48, 0x79, 0x01, 0x8f, 0x17, 0x24, 0x3f, 0x9c, + 0x93, 0xb9, 0x89, 0x51, 0x58, 0xc4, 0x51, 0xf8, 0xaa, 0x3f, 0x0a, 0x33, 0x85, 0x34, 0x15, 0x2f, + 0x29, 0x06, 0x65, 0xac, 0x19, 0x89, 0x41, 0x99, 0xa8, 0xf1, 0xe2, 0x0c, 0x9c, 0x4d, 0xe5, 0xb5, + 0x27, 0xad, 0xfe, 0xe7, 0x05, 0x99, 0x4b, 0xcd, 0x36, 0x82, 0xce, 0x5c, 0x96, 0x3b, 0xb3, 0x66, + 0x1b, 0x38, 0xd5, 0xe7, 0xc2, 0xb5, 0x53, 0x6a, 0x6c, 0xc7, 0x36, 0xe2, 0xb3, 0x7e, 0xb2, 0x2c, + 0xf9, 0x3c, 0x9c, 0x4b, 0x00, 0xf9, 0x74, 0xcd, 0xb5, 0xff, 0xf2, 0xf6, 0xd6, 0xa4, 0x9a, 0xc2, + 0x35, 0x3e, 0x7b, 0x67, 0x70, 0x21, 0x3a, 0x9c, 0x97, 0xa4, 0x6e, 0x5b, 0x9e, 0x6e, 0x5a, 0xc2, + 0xb8, 0xe4, 0xa3, 0xe4, 0x63, 0xdb, 0x5b, 0x93, 0xcf, 0xca, 0x3a, 0xe8, 0xd3, 0xc4, 0x1b, 0x9f, + 0xc5, 0x87, 0x18, 0xa0, 0xa4, 0xa0, 0xaa, 0x6d, 0x7d, 0xcd, 0xb7, 0x98, 0xaf, 0x6c, 0x6f, 0x4d, + 0x7e, 0x24, 0xb5, 0x0e, 0x93, 0x51, 0xc9, 0x2b, 0x74, 0x16, 0x27, 0xa2, 0x01, 0x09, 0x71, 0x4b, + 0xb6, 0x41, 0xf1, 0x1b, 0xfa, 0x91, 0xbf, 0xba, 0xbd, 0x35, 0xf9, 0x94, 0xc4, 0xdf, 0xb2, 0x0d, + 0x1a, 0x6f, 0x7e, 0x4a, 0x69, 0xf5, 0xd7, 0x0b, 0xf0, 0x54, 0xbd, 0xbc, 0xb8, 0x50, 0x35, 0x7c, + 0x93, 0xa6, 0xe6, 0xd8, 0x0f, 0x4d, 0x43, 0x1a, 0xbd, 0xab, 0x70, 0x3e, 0x86, 0x9a, 0x45, 0x2b, + 0x2a, 0x30, 0xa6, 0xf1, 0xdb, 0x7c, 0x73, 0xa9, 0x23, 0x68, 0x1a, 0xdc, 0xd4, 0x8a, 0x2e, 0xda, + 0x59, 0x8c, 0x58, 0x1f, 0xc5, 0x50, 0xf5, 0x75, 0xdb, 0xf1, 0x9a, 0x5d, 0x4f, 0x28, 0x01, 0xf6, + 0x51, 0xa2, 0x0e, 0x57, 0x10, 0xf5, 0xa8, 0xc2, 0xe7, 0x43, 0x7e, 0x2c, 0x07, 0x13, 0x65, 0xcf, + 0x73, 0xcc, 0xd5, 0xae, 0x47, 0x17, 0xf5, 0x4e, 0xc7, 0xb4, 0xd6, 0x70, 0xac, 0x0f, 0x5f, 0x7f, + 0x33, 0x58, 0x23, 0x7b, 0x4a, 0x62, 0x2a, 0x5e, 0x5c, 0x1a, 0xa2, 0xba, 0x8f, 0x6a, 0xb4, 0x39, + 0x4e, 0x1e, 0xa2, 0xf1, 0x72, 0x6c, 0x88, 0xa6, 0xf2, 0xda, 0xd3, 0x10, 0xfd, 0x4a, 0x01, 0x2e, + 0x2d, 0x3f, 0xf0, 0x74, 0x8d, 0xba, 0x76, 0xd7, 0x69, 0x52, 0xf7, 0x4e, 0xc7, 0xd0, 0x3d, 0x1a, + 0x8e, 0xd4, 0x49, 0xe8, 0x2f, 0x1b, 0x06, 0x35, 0x90, 0x5d, 0x3f, 0xdf, 0xf6, 0xe9, 0x0c, 0xa0, + 0x71, 0x38, 0xf9, 0x28, 0x0c, 0x88, 0x32, 0xc8, 0xbd, 0x7f, 0x7a, 0x78, 0x7b, 0x6b, 0x72, 0xa0, + 0xcb, 0x41, 0x9a, 0x8f, 0x63, 0x64, 0x15, 0xda, 0xa2, 0x8c, 0xac, 0x10, 0x92, 0x19, 0x1c, 0xa4, + 0xf9, 0x38, 0xf2, 0x0e, 0x8c, 0x21, 0xdb, 0xa0, 0x3d, 0x62, 0xee, 0x3b, 0xe3, 0x4b, 0x57, 0x6e, + 0x2c, 0x5f, 0x9a, 0xb0, 0x35, 0x0d, 0xc7, 0x2f, 0xa0, 0xc5, 0x18, 0x90, 0x7b, 0x30, 0x21, 0x1a, + 0x11, 0x32, 0xed, 0xef, 0xc1, 0xf4, 0xec, 0xf6, 0xd6, 0xe4, 0x29, 0xd1, 0x7e, 0x89, 0x6d, 0x82, + 0x09, 0x63, 0x2c, 0x9a, 0x1d, 0x32, 0x2e, 0xed, 0xc4, 0x58, 0x7c, 0xb1, 0xcc, 0x38, 0xce, 0x44, + 0x7d, 0x17, 0x46, 0xe4, 0x82, 0xe4, 0x1c, 0x6e, 0xad, 0xf9, 0x38, 0xc1, 0x4d, 0xb9, 0x69, 0xe0, + 0x7e, 0xfa, 0x45, 0x18, 0xae, 0x50, 0xb7, 0xe9, 0x98, 0x1d, 0x66, 0x35, 0x08, 0x25, 0x1f, 0xdf, + 0xde, 0x9a, 0x1c, 0x36, 0x42, 0xb0, 0x26, 0xd3, 0xa8, 0xff, 0x77, 0x0e, 0xce, 0x31, 0xde, 0x65, + 0xd7, 0x35, 0xd7, 0xac, 0xb6, 0xbc, 0x6c, 0x3f, 0x0f, 0xa5, 0x3a, 0xd6, 0x27, 0x6a, 0x3a, 0xb3, + 0xbd, 0x35, 0x39, 0xc1, 0x5b, 0x20, 0xe9, 0xa1, 0xa0, 0x09, 0xf6, 0x95, 0xf9, 0x1d, 0xf6, 0x95, + 0xcc, 0xa4, 0xf5, 0x74, 0xc7, 0x33, 0xad, 0xb5, 0xba, 0xa7, 0x7b, 0x5d, 0x37, 0x62, 0xd2, 0x0a, + 0x4c, 0xc3, 0x45, 0x54, 0xc4, 0xa4, 0x8d, 0x14, 0x22, 0x6f, 0xc3, 0xc8, 0xac, 0x65, 0x84, 0x4c, + 0xf8, 0x84, 0xf8, 0x04, 0xb3, 0x34, 0x29, 0xc2, 0x93, 0x2c, 0x22, 0x05, 0xd4, 0xbf, 0x95, 0x03, + 0x85, 0x6f, 0x02, 0x17, 0x4c, 0xd7, 0x5b, 0xa4, 0xed, 0x55, 0x69, 0x76, 0x9a, 0xf3, 0x77, 0x95, + 0x0c, 0x27, 0xad, 0x45, 0x68, 0x0a, 0x88, 0x5d, 0x65, 0xcb, 0x74, 0x13, 0xdb, 0x8f, 0x58, 0x29, + 0x52, 0x85, 0x01, 0xce, 0x99, 0xdb, 0x12, 0xc3, 0xd7, 0x15, 0x5f, 0x11, 0xe2, 0x55, 0x73, 0x65, + 0x68, 0x73, 0x62, 0x79, 0x43, 0x23, 0xca, 0xab, 0x5f, 0x2b, 0xc0, 0x44, 0xbc, 0x10, 0xb9, 0x07, + 0x83, 0xb7, 0x6c, 0xd3, 0xa2, 0xc6, 0xb2, 0x85, 0x2d, 0xec, 0x7d, 0x38, 0xe2, 0xdb, 0xe2, 0xa7, + 0xdf, 0xc3, 0x32, 0x0d, 0xd9, 0x82, 0xc5, 0xb3, 0x92, 0x80, 0x19, 0xf9, 0x2c, 0x0c, 0x31, 0x1b, + 0xf0, 0x21, 0x72, 0xce, 0xef, 0xc8, 0xf9, 0x69, 0xc1, 0xf9, 0x8c, 0xc3, 0x0b, 0x25, 0x59, 0x87, + 0xec, 0x98, 0x5e, 0x69, 0x54, 0x77, 0x6d, 0x4b, 0xf4, 0x3c, 0xea, 0x95, 0x83, 0x10, 0x59, 0xaf, + 0x38, 0x0d, 0x33, 0x5d, 0xf9, 0xc7, 0x62, 0x37, 0x48, 0x7b, 0x17, 0x2e, 0xab, 0x78, 0x0f, 0x48, + 0xc4, 0xc4, 0x82, 0x71, 0x21, 0xd0, 0x75, 0xb3, 0x83, 0x56, 0x3f, 0xae, 0x6b, 0x63, 0xd7, 0x2f, + 0x4f, 0xf9, 0x87, 0x62, 0x53, 0xd2, 0x91, 0xda, 0xc3, 0x17, 0xa7, 0x16, 0x03, 0x72, 0xdc, 0x99, + 0xa2, 0x4e, 0xc6, 0x58, 0xc8, 0xbd, 0xdd, 0x8e, 0x90, 0xab, 0x3f, 0x92, 0x87, 0x17, 0xc2, 0x2e, + 0xd2, 0xe8, 0x43, 0x93, 0x6e, 0x84, 0x1c, 0xc5, 0x1e, 0x99, 0x0d, 0x31, 0x77, 0x66, 0x5d, 0xb7, + 0xd6, 0xa8, 0x41, 0xae, 0x42, 0xbf, 0x66, 0xb7, 0xa8, 0xab, 0xe4, 0xd0, 0x3c, 0xc4, 0xe9, 0xcb, + 0x61, 0x00, 0xf9, 0x90, 0x05, 0x29, 0x88, 0x0d, 0xa5, 0x15, 0x47, 0x37, 0x3d, 0x5f, 0x93, 0xca, + 0x49, 0x4d, 0xda, 0x45, 0x8d, 0x53, 0x9c, 0x07, 0x5f, 0x63, 0x50, 0xf0, 0x1e, 0x02, 0x64, 0xc1, + 0x73, 0x92, 0x8b, 0xaf, 0xc1, 0xb0, 0x44, 0xbc, 0xa7, 0x45, 0xe4, 0x1b, 0x45, 0x79, 0x6c, 0xf9, + 0xcd, 0x12, 0x63, 0xeb, 0x1a, 0x1b, 0x13, 0xae, 0xcb, 0xac, 0x18, 0x3e, 0xa8, 0x84, 0xe6, 0x23, + 0x28, 0xaa, 0xf9, 0x08, 0x22, 0x2f, 0xc1, 0x20, 0x67, 0x11, 0xec, 0x97, 0x71, 0xaf, 0xed, 0x20, + 0x2c, 0x6a, 0x0a, 0x04, 0x84, 0xe4, 0x17, 0x72, 0xf0, 0x64, 0x4f, 0x49, 0xa0, 0xf2, 0x0d, 0x5f, + 0x7f, 0x79, 0x5f, 0x62, 0x9c, 0x7e, 0x61, 0x7b, 0x6b, 0xf2, 0xaa, 0xa4, 0x19, 0x8e, 0x44, 0xd3, + 0x68, 0x72, 0x22, 0xa9, 0x5d, 0xbd, 0x9b, 0xc2, 0x8c, 0x55, 0x5e, 0xe9, 0x1c, 0x1e, 0x55, 0x59, + 0xcd, 0x4d, 0xbf, 0x91, 0xc5, 0xd0, 0x58, 0x15, 0xdf, 0x7b, 0xdf, 0x27, 0x49, 0xa9, 0x26, 0x83, + 0x0b, 0x69, 0xc2, 0x79, 0x8e, 0xa9, 0xe8, 0x9b, 0xcb, 0xf7, 0x17, 0x6d, 0xcb, 0x5b, 0xf7, 0x2b, + 0xe8, 0x97, 0xcf, 0x7a, 0xb0, 0x02, 0x43, 0xdf, 0x6c, 0xd8, 0xf7, 0x1b, 0x6d, 0x46, 0x95, 0x52, + 0x47, 0x16, 0x27, 0x36, 0xb1, 0x8b, 0x31, 0xee, 0x4f, 0x79, 0xa5, 0xf0, 0x24, 0xce, 0x9f, 0x17, + 0x92, 0x13, 0x5c, 0xac, 0x90, 0x5a, 0x85, 0x91, 0x05, 0xbb, 0xf9, 0x20, 0x50, 0x97, 0xd7, 0xa0, + 0xb4, 0xa2, 0x3b, 0x6b, 0xd4, 0x43, 0x59, 0x0c, 0x5f, 0x3f, 0x35, 0xc5, 0x4f, 0xb7, 0x19, 0x11, + 0x47, 0x4c, 0x8f, 0x89, 0xd9, 0xa7, 0xe4, 0xe1, 0x6f, 0x4d, 0x14, 0x50, 0xbf, 0xd3, 0x0f, 0x23, + 0xe2, 0x24, 0x16, 0x57, 0x0f, 0xf2, 0x7a, 0x78, 0xb6, 0x2d, 0xa6, 0xcb, 0xe0, 0x34, 0x2a, 0x38, + 0x45, 0x1b, 0x61, 0xcc, 0xfe, 0x60, 0x6b, 0x32, 0xb7, 0xbd, 0x35, 0xd9, 0xa7, 0x0d, 0x4a, 0x9b, + 0xd8, 0x70, 0x7d, 0x93, 0x16, 0x74, 0xf9, 0x6c, 0x35, 0x56, 0x96, 0xaf, 0x77, 0x6f, 0xc3, 0x80, + 0x68, 0x83, 0xd0, 0xb8, 0xf3, 0xe1, 0xd9, 0x49, 0xe4, 0x44, 0x39, 0x56, 0xda, 0x2f, 0x45, 0xde, + 0x84, 0x12, 0x3f, 0x4b, 0x10, 0x02, 0x38, 0x97, 0x7e, 0xf6, 0x12, 0x2b, 0x2e, 0xca, 0x90, 0x79, + 0x80, 0xf0, 0x1c, 0x21, 0x38, 0x40, 0x17, 0x1c, 0x92, 0x27, 0x0c, 0x31, 0x2e, 0x52, 0x59, 0xf2, + 0x0a, 0x8c, 0xac, 0x50, 0xa7, 0x6d, 0x5a, 0x7a, 0xab, 0x6e, 0xbe, 0xef, 0x9f, 0xa1, 0xe3, 0x42, + 0xef, 0x9a, 0xef, 0xcb, 0x23, 0x37, 0x42, 0x47, 0x3e, 0x97, 0xb6, 0x4f, 0x1f, 0xc0, 0x86, 0x3c, + 0xb3, 0xe3, 0x06, 0x36, 0xd6, 0x9e, 0x94, 0x6d, 0xfb, 0x3b, 0x30, 0x1a, 0xd9, 0xa2, 0x89, 0x43, + 0xd2, 0x27, 0x93, 0xac, 0xa5, 0xfd, 0x66, 0x8c, 0x6d, 0x94, 0x03, 0xd3, 0xe4, 0xaa, 0x65, 0x7a, + 0xa6, 0xde, 0x9a, 0xb1, 0xdb, 0x6d, 0xdd, 0x32, 0x94, 0xa1, 0x50, 0x93, 0x4d, 0x8e, 0x69, 0x34, + 0x39, 0x4a, 0xd6, 0xe4, 0x68, 0x21, 0x72, 0x1b, 0x26, 0x44, 0x1f, 0x6a, 0xb4, 0x69, 0x3b, 0xcc, + 0xf6, 0xc0, 0x33, 0x50, 0x71, 0x0c, 0xe0, 0x72, 0x5c, 0xc3, 0xf1, 0x91, 0xb2, 0x71, 0x1f, 0x2f, + 0x78, 0xab, 0x38, 0x38, 0x3c, 0x31, 0x12, 0x3f, 0xb6, 0x56, 0xff, 0x46, 0x01, 0x86, 0x05, 0x29, + 0x5b, 0xba, 0x4f, 0x14, 0xfc, 0x20, 0x0a, 0x9e, 0xaa, 0xa8, 0xa5, 0xc3, 0x52, 0x54, 0xf5, 0x8b, + 0xf9, 0x60, 0x36, 0xaa, 0x39, 0xa6, 0x75, 0xb0, 0xd9, 0xe8, 0x32, 0xc0, 0xcc, 0x7a, 0xd7, 0x7a, + 0xc0, 0xaf, 0xe7, 0xf2, 0xe1, 0xf5, 0x5c, 0xd3, 0xd4, 0x24, 0x0c, 0x79, 0x12, 0x8a, 0x15, 0xc6, + 0x9f, 0xf5, 0xcc, 0xc8, 0xf4, 0xd0, 0xb7, 0x39, 0xa7, 0xdc, 0x0b, 0x1a, 0x82, 0xd9, 0x66, 0x6e, + 0x7a, 0xd3, 0xa3, 0xdc, 0x7c, 0x2e, 0xf0, 0xcd, 0xdc, 0x2a, 0x03, 0x68, 0x1c, 0x4e, 0x6e, 0xc0, + 0xa9, 0x0a, 0x6d, 0xe9, 0x9b, 0x8b, 0x66, 0xab, 0x65, 0xba, 0xb4, 0x69, 0x5b, 0x86, 0x8b, 0x42, + 0x16, 0xd5, 0xb5, 0x5d, 0x2d, 0x49, 0x40, 0x54, 0x28, 0x2d, 0xdf, 0xbf, 0xef, 0x52, 0x0f, 0xc5, + 0x57, 0x98, 0x06, 0x36, 0x39, 0xdb, 0x08, 0xd1, 0x04, 0x46, 0xfd, 0x7a, 0x8e, 0xed, 0x96, 0xdc, + 0x07, 0x9e, 0xdd, 0x09, 0xb4, 0xfc, 0x40, 0x22, 0xb9, 0x1a, 0xda, 0x15, 0x79, 0xfc, 0xda, 0x71, + 0xf1, 0xb5, 0x03, 0xc2, 0xb6, 0x08, 0x2d, 0x8a, 0xd4, 0xaf, 0x2a, 0xec, 0xf0, 0x55, 0xea, 0x1f, + 0xe7, 0xe1, 0xbc, 0x68, 0xf1, 0x4c, 0xcb, 0xec, 0xac, 0xda, 0xba, 0x63, 0x68, 0xb4, 0x49, 0xcd, + 0x87, 0xf4, 0x78, 0x0e, 0xbc, 0xe8, 0xd0, 0x29, 0x1e, 0x60, 0xe8, 0x5c, 0xc7, 0x8d, 0x27, 0x93, + 0x0c, 0x1e, 0x30, 0x73, 0xa3, 0x62, 0x62, 0x7b, 0x6b, 0x72, 0xc4, 0xe0, 0x60, 0xbc, 0x62, 0xd0, + 0x64, 0x22, 0xa6, 0x24, 0x0b, 0xd4, 0x5a, 0xf3, 0xd6, 0x51, 0x49, 0xfa, 0xb9, 0x92, 0xb4, 0x10, + 0xa2, 0x09, 0x8c, 0xfa, 0xbf, 0xe5, 0xe1, 0x4c, 0x5c, 0xe4, 0x75, 0x6a, 0x19, 0x27, 0xf2, 0xfe, + 0x60, 0xe4, 0xfd, 0x27, 0x05, 0x78, 0x42, 0x94, 0xa9, 0xaf, 0xeb, 0x0e, 0x35, 0x2a, 0xa6, 0x43, + 0x9b, 0x9e, 0xed, 0x6c, 0x1e, 0x63, 0x03, 0xea, 0xf0, 0xc4, 0x7e, 0x03, 0x4a, 0xe2, 0xb8, 0x81, + 0xaf, 0x33, 0x63, 0x41, 0x4b, 0x10, 0x9a, 0x58, 0xa1, 0xf8, 0x51, 0x45, 0xac, 0xb3, 0x4a, 0xbb, + 0xe9, 0xac, 0x4f, 0xc2, 0x68, 0x20, 0x7a, 0xdc, 0xf8, 0x0e, 0x84, 0xd6, 0x96, 0xe1, 0x23, 0x70, + 0xef, 0xab, 0x45, 0x09, 0xb1, 0x36, 0x1f, 0x50, 0xad, 0xa0, 0x35, 0x34, 0x2a, 0x6a, 0x0b, 0xca, + 0x99, 0x86, 0x26, 0x13, 0xa9, 0x5b, 0x45, 0xb8, 0x98, 0xde, 0xed, 0x1a, 0xd5, 0x8d, 0x93, 0x5e, + 0xff, 0x9e, 0xec, 0x75, 0xf2, 0x0c, 0x14, 0x6b, 0xba, 0xb7, 0x2e, 0xae, 0xfb, 0xf1, 0x0e, 0xfa, + 0xbe, 0xd9, 0xa2, 0x8d, 0x8e, 0xee, 0xad, 0x6b, 0x88, 0x92, 0xe6, 0x0c, 0x40, 0x8e, 0x29, 0x73, + 0x86, 0xb4, 0xd8, 0x0f, 0x3f, 0x9d, 0xbb, 0x52, 0x4c, 0x5d, 0xec, 0xbf, 0x53, 0xcc, 0x9a, 0x57, + 0xee, 0x39, 0xa6, 0x47, 0x4f, 0x34, 0xec, 0x44, 0xc3, 0x0e, 0xa8, 0x61, 0xff, 0x28, 0x0f, 0xa3, + 0xc1, 0xa6, 0xe9, 0x3d, 0xda, 0x3c, 0x9a, 0xb5, 0x2a, 0xdc, 0xca, 0x14, 0x0e, 0xbc, 0x95, 0x39, + 0x88, 0x42, 0xa9, 0xc1, 0x11, 0x2b, 0x37, 0x0d, 0x50, 0x62, 0xfc, 0x88, 0x35, 0x38, 0x58, 0x7d, + 0x06, 0x06, 0x16, 0xf5, 0x47, 0x66, 0xbb, 0xdb, 0x16, 0x56, 0x3a, 0xba, 0xaf, 0xb5, 0xf5, 0x47, + 0x9a, 0x0f, 0x57, 0xff, 0x9b, 0x1c, 0x8c, 0x09, 0xa1, 0x0a, 0xe6, 0x07, 0x92, 0x6a, 0x28, 0x9d, + 0xfc, 0x81, 0xa5, 0x53, 0xd8, 0xbf, 0x74, 0xd4, 0xbf, 0x52, 0x00, 0x65, 0xce, 0x6c, 0xd1, 0x15, + 0x47, 0xb7, 0xdc, 0xfb, 0xd4, 0x11, 0xdb, 0xe9, 0x59, 0xc6, 0xea, 0x40, 0x1f, 0x28, 0x4d, 0x29, + 0xf9, 0x7d, 0x4d, 0x29, 0x1f, 0x87, 0x21, 0xd1, 0x98, 0xc0, 0x75, 0x12, 0x47, 0x8d, 0xe3, 0x03, + 0xb5, 0x10, 0xcf, 0x88, 0xcb, 0x9d, 0x8e, 0x63, 0x3f, 0xa4, 0x0e, 0xbf, 0x15, 0x13, 0xc4, 0xba, + 0x0f, 0xd4, 0x42, 0xbc, 0xc4, 0x99, 0xfa, 0xf6, 0xa2, 0xcc, 0x99, 0x3a, 0x5a, 0x88, 0x27, 0x57, + 0x60, 0x70, 0xc1, 0x6e, 0xea, 0x28, 0x68, 0x3e, 0xad, 0x8c, 0x6c, 0x6f, 0x4d, 0x0e, 0xb6, 0x04, + 0x4c, 0x0b, 0xb0, 0x8c, 0xb2, 0x62, 0x6f, 0x58, 0x2d, 0x5b, 0xe7, 0xce, 0x36, 0x83, 0x9c, 0xd2, + 0x10, 0x30, 0x2d, 0xc0, 0x32, 0x4a, 0x26, 0x73, 0x74, 0x62, 0x1a, 0x0c, 0x79, 0xde, 0x17, 0x30, + 0x2d, 0xc0, 0xaa, 0x5f, 0x2f, 0x32, 0xed, 0x75, 0xcd, 0xf7, 0x1f, 0xfb, 0x75, 0x21, 0x1c, 0x30, + 0xfd, 0xfb, 0x18, 0x30, 0x8f, 0xcd, 0x81, 0x9d, 0xfa, 0x2f, 0x06, 0x00, 0x84, 0xf4, 0x67, 0x4f, + 0x36, 0x87, 0x07, 0xd3, 0x9a, 0x0a, 0x9c, 0x9a, 0xb5, 0xd6, 0x75, 0xab, 0x49, 0x8d, 0xf0, 0xd8, + 0xb2, 0x84, 0x43, 0x1b, 0x9d, 0x2e, 0xa9, 0x40, 0x86, 0xe7, 0x96, 0x5a, 0xb2, 0x00, 0x79, 0x11, + 0x86, 0xab, 0x96, 0x47, 0x1d, 0xbd, 0xe9, 0x99, 0x0f, 0xa9, 0x98, 0x1a, 0xf0, 0x26, 0xda, 0x0c, + 0xc1, 0x9a, 0x4c, 0x43, 0x6e, 0xc0, 0x48, 0x4d, 0x77, 0x3c, 0xb3, 0x69, 0x76, 0x74, 0xcb, 0x73, + 0x95, 0x41, 0x9c, 0xd1, 0xd0, 0xc2, 0xe8, 0x48, 0x70, 0x2d, 0x42, 0x45, 0x3e, 0x07, 0x43, 0xb8, + 0x35, 0x45, 0xff, 0xf0, 0xa1, 0x1d, 0x2f, 0x2a, 0x9f, 0x0d, 0xdd, 0x11, 0xf9, 0xe9, 0x2b, 0xde, + 0x38, 0xc7, 0xef, 0x2a, 0x03, 0x8e, 0xe4, 0x33, 0x30, 0x30, 0x6b, 0x19, 0xc8, 0x1c, 0x76, 0x64, + 0xae, 0x0a, 0xe6, 0xe7, 0x42, 0xe6, 0x76, 0x27, 0xc6, 0xdb, 0x67, 0x97, 0x3e, 0xca, 0x86, 0x3f, + 0xb8, 0x51, 0x36, 0xf2, 0x01, 0x1c, 0x8b, 0x8f, 0x1e, 0xd6, 0xb1, 0xf8, 0xd8, 0x3e, 0x8f, 0xc5, + 0xd5, 0xf7, 0x61, 0x78, 0xba, 0x36, 0x17, 0x8c, 0xde, 0x0b, 0x50, 0xa8, 0x09, 0xcf, 0x88, 0x22, + 0xb7, 0x67, 0x3a, 0xa6, 0xa1, 0x31, 0x18, 0xb9, 0x0a, 0x83, 0x33, 0xe8, 0x6e, 0x27, 0x6e, 0x11, + 0x8b, 0x7c, 0xfd, 0x6b, 0x22, 0x0c, 0xbd, 0x6e, 0x7d, 0x34, 0xf9, 0x28, 0x0c, 0xd4, 0x1c, 0x7b, + 0xcd, 0xd1, 0xdb, 0x62, 0x0d, 0x46, 0xd7, 0x94, 0x0e, 0x07, 0x69, 0x3e, 0x4e, 0xfd, 0xc9, 0x9c, + 0x6f, 0xb6, 0xb3, 0x12, 0xf5, 0x2e, 0x1e, 0xcd, 0x63, 0xdd, 0x83, 0xbc, 0x84, 0xcb, 0x41, 0x9a, + 0x8f, 0x23, 0x57, 0xa1, 0x7f, 0xd6, 0x71, 0x6c, 0x47, 0xf6, 0xa9, 0xa7, 0x0c, 0x20, 0x5f, 0xf7, + 0x22, 0x05, 0x79, 0x15, 0x86, 0xf9, 0x9c, 0xc3, 0x4f, 0x34, 0x0b, 0xbd, 0x6e, 0x4a, 0x65, 0x4a, + 0xf5, 0x5b, 0x05, 0xc9, 0x66, 0xe3, 0x12, 0x7f, 0x0c, 0x6f, 0x05, 0x5e, 0x82, 0xc2, 0x74, 0x6d, + 0x4e, 0x4c, 0x80, 0xa7, 0xfd, 0xa2, 0x92, 0xaa, 0xc4, 0xca, 0x31, 0x6a, 0x72, 0x09, 0x8a, 0x35, + 0xa6, 0x3e, 0x25, 0x54, 0x8f, 0xc1, 0xed, 0xad, 0xc9, 0x62, 0x87, 0xe9, 0x0f, 0x42, 0x11, 0xcb, + 0x36, 0x33, 0x7c, 0xc7, 0xc4, 0xb1, 0xe1, 0x3e, 0xe6, 0x12, 0x14, 0xcb, 0xce, 0xda, 0x43, 0x31, + 0x6b, 0x21, 0x56, 0x77, 0xd6, 0x1e, 0x6a, 0x08, 0x25, 0xd7, 0x00, 0x34, 0xea, 0x75, 0x1d, 0x0b, + 0x9f, 0xbb, 0x0c, 0xe1, 0xf9, 0x1b, 0xce, 0x86, 0x0e, 0x42, 0x1b, 0x4d, 0xdb, 0xa0, 0x9a, 0x44, + 0xa2, 0xfe, 0xf5, 0xf0, 0x62, 0xa7, 0x62, 0xba, 0x0f, 0x4e, 0xba, 0x70, 0x0f, 0x5d, 0xa8, 0x8b, + 0x23, 0xce, 0x64, 0x27, 0x4d, 0x42, 0xff, 0x5c, 0x4b, 0x5f, 0x73, 0xb1, 0x0f, 0x85, 0xef, 0xda, + 0x7d, 0x06, 0xd0, 0x38, 0x3c, 0xd6, 0x4f, 0x83, 0x3b, 0xf7, 0xd3, 0x57, 0xfb, 0x83, 0xd1, 0xb6, + 0x44, 0xbd, 0x0d, 0xdb, 0x39, 0xe9, 0xaa, 0xdd, 0x76, 0xd5, 0x65, 0x18, 0xa8, 0x3b, 0x4d, 0xe9, + 0xe8, 0x02, 0xf7, 0x03, 0xae, 0xd3, 0xe4, 0xc7, 0x16, 0x3e, 0x92, 0xd1, 0x55, 0x5c, 0x0f, 0xe9, + 0x06, 0x42, 0x3a, 0xc3, 0xf5, 0x04, 0x9d, 0x40, 0x0a, 0xba, 0x9a, 0xed, 0x78, 0xa2, 0xe3, 0x02, + 0xba, 0x8e, 0xed, 0x78, 0x9a, 0x8f, 0x24, 0x1f, 0x07, 0x58, 0x99, 0xa9, 0xf9, 0xce, 0xfd, 0x43, + 0xa1, 0xef, 0xa1, 0xf0, 0xea, 0xd7, 0x24, 0x34, 0x59, 0x81, 0xa1, 0xe5, 0x0e, 0x75, 0xf8, 0x56, + 0x88, 0x3f, 0x60, 0xf9, 0x58, 0x4c, 0xb4, 0xa2, 0xdf, 0xa7, 0xc4, 0xff, 0x01, 0x39, 0x5f, 0x5f, + 0x6c, 0xff, 0xa7, 0x16, 0x32, 0x22, 0xaf, 0x42, 0xa9, 0xcc, 0xed, 0xbc, 0x61, 0x64, 0x19, 0x88, + 0x0c, 0xb7, 0xa0, 0x1c, 0xc5, 0xf7, 0xec, 0x3a, 0xfe, 0xad, 0x09, 0x72, 0xf5, 0x2a, 0x4c, 0xc4, + 0xab, 0x21, 0xc3, 0x30, 0x30, 0xb3, 0xbc, 0xb4, 0x34, 0x3b, 0xb3, 0x32, 0xd1, 0x47, 0x06, 0xa1, + 0x58, 0x9f, 0x5d, 0xaa, 0x4c, 0xe4, 0xd4, 0x5f, 0x94, 0x66, 0x10, 0xa6, 0x5a, 0x27, 0x57, 0xc3, + 0x07, 0xba, 0x6f, 0x99, 0xc0, 0xfb, 0x50, 0x3c, 0x31, 0x68, 0x9b, 0x9e, 0x47, 0x0d, 0xb1, 0x4a, + 0xe0, 0x7d, 0xa1, 0xf7, 0x48, 0x4b, 0xe0, 0xc9, 0xf3, 0x30, 0x8a, 0x30, 0x71, 0x45, 0xc8, 0xf7, + 0xc7, 0xa2, 0x80, 0xf3, 0x48, 0x8b, 0x22, 0xd5, 0xdf, 0x0b, 0x6f, 0x87, 0x17, 0xa8, 0x7e, 0x5c, + 0x6f, 0x14, 0x3f, 0x24, 0xfd, 0xa5, 0xfe, 0xcb, 0x22, 0x7f, 0x72, 0xc2, 0xdf, 0x27, 0x1e, 0x85, + 0x28, 0xc3, 0x23, 0xdd, 0xc2, 0x1e, 0x8e, 0x74, 0x9f, 0x87, 0xd2, 0x22, 0xf5, 0xd6, 0x6d, 0xdf, + 0xf1, 0x0b, 0x3d, 0xf4, 0xda, 0x08, 0x91, 0x3d, 0xf4, 0x38, 0x0d, 0x79, 0x00, 0xc4, 0x7f, 0x7c, + 0x18, 0x38, 0x7e, 0xfb, 0x47, 0xc8, 0xe7, 0x13, 0xfb, 0x94, 0x3a, 0x3e, 0x51, 0x46, 0x9f, 0xfe, + 0x33, 0x81, 0x63, 0xb9, 0xe4, 0x89, 0xf5, 0xa7, 0x5b, 0x93, 0x25, 0x4e, 0xa3, 0xa5, 0xb0, 0x25, + 0xef, 0xc0, 0xd0, 0xe2, 0x5c, 0x59, 0x3c, 0x44, 0xe4, 0x5e, 0x11, 0x17, 0x02, 0x29, 0xfa, 0x88, + 0x40, 0x24, 0xf8, 0xbe, 0xa7, 0x7d, 0x5f, 0x4f, 0xbe, 0x43, 0x0c, 0xb9, 0x30, 0x6d, 0xe1, 0x2f, + 0x85, 0xc4, 0xe9, 0x42, 0xa0, 0x2d, 0xd1, 0xf7, 0x43, 0x71, 0x59, 0x71, 0x6c, 0x4c, 0x5b, 0x06, + 0x0f, 0x30, 0xba, 0x97, 0xe1, 0x54, 0xb9, 0xd3, 0x69, 0x99, 0xd4, 0x40, 0x7d, 0xd1, 0xba, 0x2d, + 0xea, 0x0a, 0x97, 0x1f, 0x7c, 0x7c, 0xa2, 0x73, 0x64, 0x03, 0x9f, 0xbf, 0x36, 0x9c, 0x6e, 0xd4, + 0x3f, 0x33, 0x59, 0x56, 0xfd, 0xe9, 0x3c, 0x9c, 0x9b, 0x71, 0xa8, 0xee, 0xd1, 0xc5, 0xb9, 0x72, + 0xb9, 0x8b, 0x3e, 0x72, 0xad, 0x16, 0xb5, 0xd6, 0x8e, 0x66, 0x58, 0xbf, 0x01, 0x63, 0x41, 0x03, + 0xea, 0x4d, 0xbb, 0x43, 0xe5, 0x87, 0x5c, 0x4d, 0x1f, 0xd3, 0x70, 0x19, 0x4a, 0x8b, 0x91, 0x92, + 0xdb, 0x70, 0x3a, 0x80, 0x94, 0x5b, 0x2d, 0x7b, 0x43, 0xa3, 0x5d, 0x97, 0x3b, 0xe2, 0x0e, 0x72, + 0x47, 0xdc, 0x90, 0x83, 0xce, 0xf0, 0x0d, 0x87, 0x11, 0x68, 0x69, 0xa5, 0xd4, 0xaf, 0x15, 0xe0, + 0xfc, 0x5d, 0xbd, 0x65, 0x1a, 0xa1, 0x68, 0x34, 0xea, 0x76, 0x6c, 0xcb, 0xa5, 0xc7, 0x68, 0x94, + 0x46, 0x86, 0x42, 0xf1, 0x50, 0x86, 0x42, 0xb2, 0x8b, 0xfa, 0x0f, 0xdc, 0x45, 0xa5, 0x7d, 0x75, + 0xd1, 0xff, 0x9a, 0x83, 0x09, 0xff, 0xa1, 0x81, 0xfc, 0x68, 0x5c, 0xf2, 0x82, 0xc7, 0x23, 0xc4, + 0x98, 0xdf, 0x35, 0xe2, 0x49, 0x1d, 0x06, 0x66, 0x1f, 0x75, 0x4c, 0x87, 0xba, 0xbb, 0x70, 0x1a, + 0x7f, 0x52, 0x1c, 0x97, 0x9c, 0xa2, 0xbc, 0x48, 0xe2, 0xa4, 0x84, 0x83, 0xf1, 0xf9, 0x20, 0x7f, + 0x6a, 0x31, 0xed, 0xbf, 0x84, 0xe7, 0xcf, 0x07, 0xc5, 0x93, 0x8c, 0xc8, 0x7b, 0xd0, 0x90, 0x94, + 0x3c, 0x0b, 0x85, 0x95, 0x95, 0x05, 0x31, 0x93, 0x62, 0x04, 0x02, 0xcf, 0x93, 0xdf, 0x47, 0x32, + 0xac, 0xfa, 0x4f, 0xf3, 0x00, 0x4c, 0x15, 0xf8, 0x70, 0x3d, 0x12, 0x25, 0x9c, 0x86, 0x41, 0x5f, + 0xe0, 0x42, 0x0d, 0x83, 0x57, 0x02, 0xf1, 0x8e, 0x88, 0xd7, 0x1d, 0xbc, 0x08, 0x99, 0xf4, 0x1d, + 0xc9, 0xf9, 0x3d, 0x00, 0xee, 0x6c, 0xd0, 0x91, 0xdc, 0x77, 0x1f, 0xff, 0x38, 0x0c, 0x89, 0x19, + 0xcf, 0x8e, 0x9c, 0xff, 0x37, 0x7d, 0xa0, 0x16, 0xe2, 0x63, 0x53, 0x6b, 0xe9, 0x00, 0x0b, 0xb1, + 0x2f, 0x5e, 0xde, 0x2b, 0x27, 0xe2, 0x3d, 0x64, 0xf1, 0x7e, 0x49, 0x88, 0x97, 0xbf, 0x18, 0x3a, + 0xb6, 0xe2, 0x3d, 0xb4, 0xb3, 0x6f, 0xf5, 0x1f, 0xe5, 0x80, 0xb0, 0x66, 0xd5, 0x74, 0xd7, 0xdd, + 0xb0, 0x1d, 0x83, 0x3b, 0xa7, 0x1f, 0x89, 0x60, 0x0e, 0xef, 0xbe, 0xf2, 0x5b, 0x83, 0x70, 0x3a, + 0xe2, 0xf8, 0x7b, 0xcc, 0x27, 0xab, 0xab, 0xd1, 0xd1, 0xd4, 0xeb, 0xd5, 0xcb, 0x47, 0xe4, 0x0b, + 0xd1, 0xfe, 0xc8, 0x83, 0x37, 0xe9, 0x26, 0xf4, 0x05, 0x18, 0x11, 0x3f, 0xd8, 0x0a, 0xed, 0xdf, + 0x74, 0xe1, 0x28, 0x75, 0x19, 0x40, 0x8b, 0xa0, 0xc9, 0xcb, 0x30, 0xc4, 0x06, 0xcc, 0x1a, 0x06, + 0x2b, 0x19, 0x08, 0x5f, 0x94, 0x18, 0x3e, 0x50, 0x5e, 0x4f, 0x02, 0x4a, 0xe9, 0xdd, 0xd2, 0xe0, + 0x2e, 0xde, 0x2d, 0x7d, 0x1e, 0x86, 0xcb, 0x96, 0x65, 0x7b, 0xb8, 0x49, 0x77, 0xc5, 0xd5, 0x44, + 0xa6, 0x55, 0xfe, 0x2c, 0x3e, 0xc6, 0x0f, 0xe9, 0x53, 0xcd, 0x72, 0x99, 0x21, 0xb9, 0xee, 0xbf, + 0x8a, 0xa1, 0x8e, 0xf0, 0x2a, 0xc7, 0xeb, 0x19, 0x47, 0xc0, 0x92, 0x8f, 0x62, 0xb0, 0xf3, 0x46, + 0x6b, 0x8e, 0xdd, 0xb1, 0x5d, 0x6a, 0x70, 0x41, 0x0d, 0x87, 0xa1, 0x0d, 0x3a, 0x02, 0x81, 0xef, + 0xe6, 0x22, 0x81, 0x43, 0x22, 0x45, 0xc8, 0x7d, 0x38, 0xe3, 0x5f, 0x14, 0x07, 0x2f, 0x14, 0xab, + 0x15, 0x57, 0x19, 0xc1, 0x57, 0x49, 0x24, 0xae, 0x0c, 0xd5, 0xca, 0xf4, 0x53, 0xfe, 0xb5, 0x88, + 0xff, 0xc4, 0xb1, 0x61, 0x1a, 0x72, 0x57, 0xa7, 0xf2, 0x23, 0x3f, 0x00, 0xc3, 0x8b, 0xfa, 0xa3, + 0x4a, 0x57, 0x9c, 0xbd, 0x8c, 0xee, 0xfe, 0xf6, 0xa5, 0xad, 0x3f, 0x6a, 0x18, 0xa2, 0x5c, 0xcc, + 0xa6, 0x90, 0x59, 0x92, 0x06, 0x9c, 0xab, 0x39, 0x76, 0xdb, 0xf6, 0xa8, 0x11, 0x7b, 0xec, 0x37, + 0x1e, 0xbe, 0x0e, 0xee, 0x08, 0x8a, 0x46, 0x8f, 0x57, 0x7f, 0x19, 0x6c, 0x48, 0x1b, 0xc6, 0xcb, + 0xae, 0xdb, 0x6d, 0xd3, 0xf0, 0x86, 0x6a, 0x62, 0xc7, 0xcf, 0xf8, 0x98, 0xf0, 0x5a, 0x7e, 0x42, + 0xc7, 0xa2, 0xfc, 0x82, 0xaa, 0xe1, 0x99, 0x72, 0x8d, 0xf8, 0x2d, 0x71, 0xde, 0xb7, 0x8a, 0x83, + 0x63, 0x13, 0xe3, 0xda, 0xf9, 0x64, 0x63, 0x56, 0x4c, 0xaf, 0x45, 0xd5, 0x6f, 0xe6, 0x00, 0x42, + 0x01, 0x93, 0x17, 0xa2, 0x11, 0x91, 0x72, 0xe1, 0x45, 0x87, 0x88, 0x96, 0x10, 0x09, 0x81, 0x44, + 0x2e, 0x41, 0x11, 0x23, 0x6a, 0xe4, 0xc3, 0x83, 0xd5, 0x07, 0xa6, 0x65, 0x68, 0x08, 0x65, 0x58, + 0xe9, 0xe9, 0x3b, 0x62, 0xf1, 0x52, 0x9f, 0x5b, 0x85, 0x15, 0x18, 0xaf, 0x77, 0x57, 0xfd, 0xba, + 0xa5, 0x77, 0x7c, 0x18, 0xd8, 0xc3, 0xed, 0xae, 0x06, 0x8f, 0x5f, 0x23, 0x61, 0x53, 0xa2, 0x45, + 0xd4, 0xaf, 0xe7, 0x62, 0xb3, 0xe0, 0x11, 0x2e, 0x7a, 0x1f, 0x49, 0xfa, 0x69, 0x24, 0xa7, 0x25, + 0xf5, 0x0f, 0x0b, 0x30, 0x5c, 0xb3, 0x1d, 0x4f, 0x84, 0x28, 0x39, 0xde, 0xab, 0x90, 0xb4, 0x57, + 0x2a, 0xee, 0x61, 0xaf, 0x74, 0x09, 0x8a, 0x92, 0x8b, 0x32, 0xbf, 0x17, 0x31, 0x0c, 0x47, 0x43, + 0xe8, 0x07, 0xfc, 0xe4, 0x22, 0x79, 0x09, 0x3a, 0x70, 0x60, 0x57, 0x83, 0x1f, 0xca, 0x03, 0x7c, + 0xe6, 0xc5, 0x17, 0x1f, 0xe3, 0x2e, 0x55, 0xff, 0x72, 0x0e, 0xc6, 0xc5, 0xd5, 0xa2, 0x14, 0x0d, + 0x6d, 0xc0, 0xbf, 0x14, 0x96, 0x67, 0x12, 0x0e, 0xd2, 0x7c, 0x1c, 0x5b, 0xb4, 0x66, 0x1f, 0x99, + 0x1e, 0xde, 0xae, 0x48, 0xe1, 0xd0, 0xa8, 0x80, 0xc9, 0x8b, 0x96, 0x4f, 0x47, 0x5e, 0xf0, 0x2f, + 0x4d, 0x0b, 0xe1, 0x4a, 0xcd, 0x0a, 0xcc, 0xa6, 0x5e, 0x9c, 0xaa, 0xbf, 0x5a, 0x84, 0xe2, 0xec, + 0x23, 0xda, 0x3c, 0xe6, 0x5d, 0x23, 0x1d, 0xc5, 0x16, 0x0f, 0x78, 0x14, 0xbb, 0x1f, 0x2f, 0x90, + 0xb7, 0xc3, 0xfe, 0x2c, 0x45, 0xab, 0x8f, 0xf5, 0x7c, 0xbc, 0x7a, 0xbf, 0xa7, 0x8f, 0x9f, 0x13, + 0xd1, 0x7f, 0x59, 0x80, 0x42, 0x7d, 0xa6, 0x76, 0xa2, 0x37, 0x47, 0xaa, 0x37, 0xbd, 0x6f, 0xd9, + 0xd5, 0xe0, 0xe2, 0x6c, 0x30, 0xf4, 0x6b, 0x8d, 0xdd, 0x91, 0xfd, 0x49, 0x01, 0xc6, 0xea, 0x73, + 0x2b, 0x35, 0xe9, 0xec, 0xfa, 0x36, 0xf7, 0x3d, 0x44, 0x2f, 0x38, 0xde, 0xa5, 0x97, 0x12, 0x16, + 0xd8, 0x9d, 0xaa, 0xe5, 0xbd, 0x72, 0xe3, 0xae, 0xde, 0xea, 0x52, 0x3c, 0x2c, 0xe2, 0x9e, 0xca, + 0xae, 0xf9, 0x3e, 0xfd, 0x1a, 0x86, 0x46, 0xf0, 0x19, 0x90, 0x37, 0xa0, 0x70, 0x47, 0xf8, 0x90, + 0x64, 0xf1, 0x79, 0xe9, 0x3a, 0xe7, 0xc3, 0x26, 0xc1, 0x42, 0xd7, 0x34, 0x90, 0x03, 0x2b, 0xc5, + 0x0a, 0xdf, 0x14, 0x26, 0xc3, 0xae, 0x0a, 0xaf, 0xf9, 0x85, 0x6f, 0x56, 0x2b, 0xa4, 0x0e, 0xc3, + 0x35, 0xea, 0xb4, 0x4d, 0xec, 0x28, 0x7f, 0xce, 0xee, 0xcd, 0x84, 0xed, 0xad, 0x86, 0x3b, 0x61, + 0x21, 0x64, 0x26, 0x73, 0x21, 0xef, 0x02, 0x70, 0xab, 0x6a, 0x97, 0x11, 0x36, 0x9f, 0xc4, 0x9d, + 0x0a, 0x37, 0x86, 0x53, 0xac, 0x52, 0x89, 0x19, 0x79, 0x00, 0x13, 0x8b, 0xb6, 0x61, 0xde, 0x37, + 0xb9, 0xb3, 0x28, 0x56, 0x50, 0xda, 0xd9, 0x45, 0x8b, 0x19, 0xbf, 0x6d, 0xa9, 0x5c, 0x5a, 0x35, + 0x09, 0xc6, 0xea, 0xdf, 0xe9, 0x87, 0x22, 0xeb, 0xf6, 0x93, 0xf1, 0x7b, 0x90, 0xf1, 0x5b, 0x86, + 0x89, 0x7b, 0xb6, 0xf3, 0xc0, 0xb4, 0xd6, 0x02, 0x3f, 0x7e, 0xb1, 0x9b, 0x46, 0xdf, 0xa3, 0x0d, + 0x8e, 0x6b, 0x04, 0x2e, 0xff, 0x5a, 0x82, 0x7c, 0x87, 0x11, 0xfc, 0x1a, 0x00, 0x7f, 0x9d, 0x8f, + 0x34, 0x83, 0x61, 0x38, 0x0f, 0xfe, 0x76, 0x1f, 0x9f, 0x06, 0xc8, 0xe1, 0x3c, 0x42, 0x62, 0x72, + 0xd5, 0xf7, 0xde, 0x18, 0xc2, 0x97, 0x02, 0x78, 0x6c, 0x80, 0xde, 0x1b, 0xb2, 0x11, 0xc0, 0xfd, + 0x38, 0x6a, 0x00, 0xd2, 0x8d, 0x18, 0xc4, 0x04, 0x11, 0x99, 0x1c, 0x44, 0x00, 0xbd, 0x94, 0x0b, + 0x31, 0x4d, 0xe2, 0x41, 0x5e, 0x89, 0x5d, 0xd9, 0x93, 0x08, 0xb7, 0xcc, 0x1b, 0xfb, 0xd0, 0xe5, + 0x6b, 0x64, 0x27, 0x97, 0x2f, 0xf5, 0x6f, 0x16, 0x60, 0x98, 0x71, 0xab, 0x77, 0xdb, 0x6d, 0xdd, + 0xd9, 0x3c, 0x51, 0xe4, 0x83, 0x28, 0x72, 0x03, 0x4e, 0xc9, 0x2e, 0xfe, 0xcc, 0x74, 0xf5, 0x83, + 0x31, 0x05, 0x07, 0x56, 0x71, 0x02, 0x6e, 0x5b, 0xe2, 0xbc, 0xef, 0x09, 0x30, 0x9e, 0x86, 0xb8, + 0x5a, 0x92, 0x97, 0xfa, 0x53, 0x39, 0x98, 0x88, 0x43, 0x03, 0xdd, 0xcf, 0xa5, 0xea, 0xfe, 0xf3, + 0x30, 0x24, 0x2e, 0xfd, 0x75, 0x43, 0xf8, 0x20, 0x8e, 0x6d, 0x6f, 0x4d, 0x02, 0xbe, 0xb8, 0x6e, + 0x38, 0x54, 0x37, 0xb4, 0x90, 0x80, 0xbc, 0x0c, 0x23, 0xf8, 0xe3, 0x9e, 0x63, 0x7a, 0x1e, 0xe5, + 0x9d, 0x51, 0xe4, 0xf7, 0x18, 0xbc, 0xc0, 0x06, 0x47, 0x68, 0x11, 0x32, 0xf5, 0x77, 0xf3, 0x30, + 0x54, 0xef, 0xae, 0xba, 0x9b, 0xae, 0x47, 0xdb, 0xc7, 0x5c, 0x87, 0xfc, 0x63, 0x85, 0x62, 0xea, + 0xb1, 0xc2, 0xb3, 0xfe, 0xd0, 0x92, 0xce, 0xdb, 0x83, 0x8d, 0x81, 0xef, 0x47, 0x19, 0x6a, 0x51, + 0x69, 0xef, 0x5a, 0xa4, 0xfe, 0xed, 0x3c, 0x4c, 0xf0, 0xeb, 0xe6, 0x8a, 0xe9, 0x36, 0x0f, 0xe1, + 0x09, 0xcc, 0xd1, 0xcb, 0xf4, 0x60, 0x2e, 0x1a, 0xbb, 0x78, 0x58, 0xa4, 0x7e, 0x21, 0x0f, 0xc3, + 0xe5, 0xae, 0xb7, 0x5e, 0xf6, 0x70, 0x7e, 0x7b, 0x2c, 0xf7, 0xc8, 0xff, 0x20, 0x07, 0xe3, 0xac, + 0x21, 0x2b, 0xf6, 0x03, 0x6a, 0x1d, 0xc2, 0x71, 0xbd, 0x7c, 0xec, 0x9e, 0xdf, 0xe7, 0xb1, 0xbb, + 0x2f, 0xcb, 0xc2, 0xde, 0x64, 0x89, 0x97, 0x4c, 0x9a, 0xdd, 0xa2, 0xc7, 0xfb, 0x33, 0x0e, 0xf1, + 0x92, 0xc9, 0x17, 0xc8, 0x21, 0x5c, 0x6a, 0x7e, 0x6f, 0x09, 0xe4, 0x10, 0x4e, 0x64, 0xbf, 0x37, + 0x04, 0xf2, 0xad, 0x1c, 0x0c, 0x4d, 0xdb, 0xde, 0x31, 0x1f, 0xf8, 0xe2, 0x2b, 0x8e, 0xb7, 0x9a, + 0xfb, 0x5f, 0x71, 0xbc, 0x75, 0x53, 0xfd, 0x99, 0x3c, 0x9c, 0x11, 0x11, 0xfc, 0xc5, 0x19, 0xd8, + 0xc9, 0x74, 0x2c, 0x06, 0x5b, 0x52, 0x34, 0x27, 0xf3, 0x90, 0x10, 0xcd, 0xcf, 0x17, 0xe0, 0x0c, + 0x06, 0x1c, 0x66, 0x3b, 0xaa, 0xef, 0x01, 0x5b, 0x84, 0x34, 0xa3, 0xae, 0x03, 0x8b, 0x29, 0xae, + 0x03, 0x7f, 0xba, 0x35, 0xf9, 0xca, 0x9a, 0xe9, 0xad, 0x77, 0x57, 0xa7, 0x9a, 0x76, 0xfb, 0xda, + 0x9a, 0xa3, 0x3f, 0x34, 0xf9, 0xa5, 0xb9, 0xde, 0xba, 0x16, 0x26, 0xd6, 0xe9, 0x98, 0x22, 0x4d, + 0x4e, 0x1d, 0x77, 0x4a, 0x8c, 0xab, 0xef, 0x74, 0xe0, 0x02, 0xdc, 0xb2, 0x4d, 0x4b, 0x78, 0xe2, + 0x72, 0x43, 0xb7, 0xbe, 0xbd, 0x35, 0x79, 0xf6, 0x3d, 0xdb, 0xb4, 0x1a, 0x71, 0x77, 0xdc, 0xbd, + 0xd6, 0x17, 0xb2, 0xd6, 0xa4, 0x6a, 0xd4, 0xff, 0x3a, 0x07, 0x17, 0xa2, 0x5a, 0xfc, 0xbd, 0x60, + 0x3b, 0xfe, 0xa5, 0x3c, 0x9c, 0xbd, 0x89, 0xc2, 0x09, 0xdc, 0x9f, 0x4e, 0xe6, 0x2d, 0x31, 0x38, + 0x53, 0x64, 0x73, 0x62, 0x51, 0x66, 0xcb, 0xe6, 0x64, 0x52, 0x17, 0xb2, 0xf9, 0x87, 0x39, 0x38, + 0xbd, 0x5c, 0xad, 0xcc, 0x7c, 0x8f, 0x8c, 0xa8, 0xe4, 0xf7, 0x1c, 0x73, 0x83, 0x33, 0xf1, 0x3d, + 0xc7, 0xdc, 0xf4, 0xfc, 0x4a, 0x1e, 0x4e, 0xd7, 0xcb, 0x8b, 0x0b, 0xdf, 0x2b, 0x33, 0xf8, 0x8c, + 0xec, 0xab, 0xeb, 0x1f, 0x82, 0x09, 0x5b, 0x40, 0xfe, 0xcc, 0xbb, 0xd7, 0xb3, 0x7d, 0x78, 0x93, + 0x42, 0x39, 0xe6, 0x53, 0xf7, 0xa1, 0x08, 0x85, 0x69, 0x7e, 0x84, 0xfa, 0x98, 0x6b, 0xfe, 0xdf, + 0x2b, 0xc1, 0xf0, 0xed, 0xee, 0x2a, 0x15, 0x2e, 0x5d, 0x8f, 0xf5, 0xc9, 0xef, 0x75, 0x18, 0x16, + 0x62, 0xc0, 0x1b, 0x0e, 0x29, 0xe4, 0xa4, 0x08, 0x21, 0xc4, 0xa3, 0x7a, 0xc9, 0x44, 0xe4, 0x12, + 0x14, 0xef, 0x52, 0x67, 0x55, 0x7e, 0x8d, 0xfd, 0x90, 0x3a, 0xab, 0x1a, 0x42, 0xc9, 0x42, 0xf8, + 0xd0, 0xa4, 0x5c, 0xab, 0x62, 0xba, 0x23, 0x71, 0x69, 0x88, 0xf9, 0x9b, 0x02, 0x6f, 0x51, 0xbd, + 0x63, 0xf2, 0x44, 0x49, 0x72, 0x24, 0x88, 0x78, 0x49, 0xb2, 0x04, 0xa7, 0x64, 0x77, 0x41, 0x9e, + 0xeb, 0x67, 0x30, 0x85, 0x5d, 0x5a, 0x96, 0x9f, 0x64, 0x51, 0xf2, 0x36, 0x8c, 0xf8, 0x40, 0x74, + 0x7c, 0x1c, 0x0a, 0x13, 0x4c, 0x04, 0xac, 0x62, 0xf9, 0x00, 0x22, 0x05, 0x64, 0x06, 0x78, 0x89, + 0x01, 0x29, 0x0c, 0x62, 0x8e, 0xa4, 0x91, 0x02, 0xe4, 0x65, 0x64, 0x80, 0x8f, 0xa3, 0xd0, 0x61, + 0x6a, 0x18, 0x9f, 0x2a, 0xe3, 0x05, 0x90, 0x23, 0xe0, 0xfc, 0x41, 0x7a, 0x84, 0x8c, 0x2c, 0x03, + 0x84, 0x8e, 0x2d, 0x22, 0xec, 0xc7, 0x9e, 0x5d, 0x6e, 0x24, 0x16, 0xf2, 0x4d, 0xde, 0xe8, 0x7e, + 0x6e, 0xf2, 0xd4, 0x9f, 0x2e, 0xc0, 0x70, 0xb9, 0xd3, 0x09, 0x86, 0xc2, 0x0b, 0x50, 0x2a, 0x77, + 0x3a, 0x77, 0xb4, 0xaa, 0x9c, 0x00, 0x40, 0xef, 0x74, 0x1a, 0x5d, 0xc7, 0x94, 0x3d, 0xa9, 0x39, + 0x11, 0x99, 0x81, 0xd1, 0x72, 0xa7, 0x53, 0xeb, 0xae, 0xb6, 0xcc, 0xa6, 0x94, 0xbf, 0x8c, 0x67, + 0x78, 0xec, 0x74, 0x1a, 0x1d, 0xc4, 0xc4, 0x93, 0xd8, 0x45, 0xcb, 0x90, 0xcf, 0x63, 0xb0, 0x2c, + 0x91, 0x3e, 0x8b, 0x27, 0xe8, 0x51, 0x83, 0xd0, 0xff, 0x61, 0xdb, 0xa6, 0x02, 0x22, 0x9e, 0x22, + 0xe1, 0x92, 0x9f, 0xd8, 0x82, 0x55, 0x94, 0x48, 0x93, 0x15, 0xb2, 0x24, 0x9f, 0x80, 0x81, 0x72, + 0xa7, 0x23, 0xdd, 0x56, 0xa1, 0x63, 0x1b, 0x2b, 0x15, 0xcf, 0x50, 0x28, 0xc8, 0xc4, 0x67, 0x89, + 0xfb, 0x6d, 0xdb, 0xf1, 0x70, 0x48, 0x8d, 0x86, 0x9f, 0xe5, 0x5f, 0x88, 0xdb, 0x72, 0x7c, 0x1a, + 0x2d, 0x5a, 0xe6, 0xe2, 0x9b, 0x30, 0x16, 0x6d, 0xf1, 0x9e, 0xf2, 0x34, 0x7c, 0x37, 0x87, 0x52, + 0x39, 0xe6, 0xcf, 0x09, 0x5e, 0x82, 0x42, 0xb9, 0xd3, 0x11, 0x93, 0xda, 0xe9, 0x94, 0x4e, 0x8d, + 0x47, 0x1f, 0x28, 0x77, 0x3a, 0xfe, 0xa7, 0x1f, 0xf3, 0x77, 0x49, 0xfb, 0xfa, 0xf4, 0x6f, 0xf1, + 0x4f, 0x3f, 0xde, 0x6f, 0x86, 0xd4, 0x5f, 0x2d, 0xc0, 0x78, 0xb9, 0xd3, 0x39, 0xc9, 0xef, 0x70, + 0x58, 0x31, 0x0e, 0x5e, 0x04, 0x90, 0xe6, 0xd8, 0x81, 0xe0, 0xd5, 0xe4, 0xb0, 0x34, 0xbf, 0x2a, + 0x39, 0x4d, 0x22, 0xf2, 0xd5, 0x6f, 0x70, 0x4f, 0xea, 0xf7, 0x85, 0x02, 0x4e, 0x7c, 0xc7, 0x3d, + 0x5e, 0xdb, 0x87, 0xa5, 0xdb, 0x44, 0x1f, 0x94, 0xf6, 0xd4, 0x07, 0xbf, 0x13, 0x19, 0x3c, 0x98, + 0x2f, 0xe0, 0xa4, 0x17, 0xfa, 0x0f, 0x64, 0x5b, 0x8f, 0xc9, 0xc2, 0x14, 0x41, 0xa4, 0xfc, 0x9c, + 0x69, 0x22, 0xa4, 0x59, 0x93, 0xa1, 0x1a, 0xa6, 0xa1, 0xc5, 0x68, 0xfd, 0x3e, 0x1c, 0xd8, 0x53, + 0x1f, 0x6e, 0xe5, 0x31, 0x6c, 0x41, 0x10, 0x12, 0xed, 0xe0, 0x5b, 0x94, 0x6b, 0x00, 0xdc, 0x7d, + 0x21, 0xf0, 0xcf, 0x1f, 0xe5, 0xd1, 0x8f, 0x78, 0x2a, 0x35, 0x11, 0xfd, 0x28, 0x24, 0x09, 0xdc, + 0x9d, 0x0a, 0xa9, 0xee, 0x4e, 0x57, 0x61, 0x50, 0xd3, 0x37, 0xde, 0xe9, 0x52, 0x67, 0x53, 0xd8, + 0x44, 0x3c, 0xe2, 0xa8, 0xbe, 0xd1, 0xf8, 0x41, 0x06, 0xd4, 0x02, 0x34, 0x51, 0x83, 0xb8, 0x17, + 0x92, 0x5b, 0x09, 0x3f, 0x68, 0x0f, 0xa2, 0x5d, 0xec, 0x47, 0xd1, 0xc9, 0xeb, 0x50, 0x28, 0xdf, + 0xab, 0x0b, 0xc9, 0x06, 0x5d, 0x5b, 0xbe, 0x57, 0x17, 0xf2, 0xca, 0x2c, 0x7b, 0xaf, 0xae, 0x7e, + 0x21, 0x0f, 0x24, 0x49, 0x49, 0x5e, 0x81, 0x21, 0x84, 0xae, 0x31, 0x9d, 0x91, 0x73, 0xf0, 0x6e, + 0xb8, 0x0d, 0x07, 0xa1, 0x11, 0x0b, 0xd1, 0x27, 0x25, 0xaf, 0x61, 0x96, 0x73, 0x91, 0x05, 0x32, + 0x92, 0x83, 0x77, 0xc3, 0xf5, 0xf3, 0x82, 0xc7, 0x92, 0x9c, 0x0b, 0x62, 0x34, 0x2e, 0xef, 0xd5, + 0xe7, 0x6d, 0xd7, 0x13, 0xa2, 0xe6, 0xc6, 0xe5, 0x86, 0x8b, 0xc9, 0x9f, 0x23, 0xc6, 0x25, 0x27, + 0xc3, 0x04, 0x76, 0xf7, 0xea, 0xfc, 0x85, 0x98, 0xa1, 0xd9, 0x2d, 0xdf, 0x2a, 0xe5, 0x09, 0xec, + 0x36, 0xdc, 0x06, 0x7f, 0x5d, 0x66, 0x60, 0x7a, 0xf5, 0x48, 0x02, 0xbb, 0x48, 0x29, 0xf5, 0x27, + 0x06, 0x61, 0xa2, 0xa2, 0x7b, 0xfa, 0xaa, 0xee, 0x52, 0x69, 0x4b, 0x3e, 0xee, 0xc3, 0xfc, 0xcf, + 0x91, 0xe4, 0x60, 0xac, 0xa6, 0x7c, 0x4d, 0xbc, 0x00, 0x79, 0x23, 0xe4, 0x1b, 0xa4, 0x17, 0x96, + 0xf3, 0x15, 0xae, 0x36, 0x3a, 0x02, 0xac, 0x25, 0x08, 0xc9, 0xf3, 0x30, 0xec, 0xc3, 0xd8, 0x2e, + 0xa2, 0x10, 0xea, 0x8c, 0xb1, 0xca, 0x36, 0x11, 0x9a, 0x8c, 0x26, 0xaf, 0xc1, 0x88, 0xff, 0x53, + 0xb2, 0xcf, 0x79, 0xf2, 0xc5, 0xd5, 0xc4, 0x16, 0x4c, 0x26, 0x95, 0x8b, 0xe2, 0xfc, 0xd6, 0x1f, + 0x29, 0x1a, 0xcb, 0x6f, 0x18, 0x21, 0x25, 0x3f, 0x08, 0x63, 0xfe, 0x6f, 0xb1, 0xeb, 0xe0, 0xde, + 0x87, 0xcf, 0x07, 0xd9, 0xdb, 0x63, 0x62, 0x9d, 0x8a, 0x92, 0xf3, 0xfd, 0xc7, 0x13, 0x7e, 0xca, + 0x3e, 0x63, 0x35, 0xb9, 0xfd, 0x88, 0x55, 0x40, 0xaa, 0x70, 0xca, 0x87, 0x84, 0x1a, 0x3a, 0x10, + 0x6e, 0x3b, 0x8d, 0xd5, 0x46, 0xaa, 0x92, 0x26, 0x4b, 0x91, 0x16, 0x5c, 0x8a, 0x00, 0x0d, 0x77, + 0xdd, 0xbc, 0xef, 0x89, 0x3d, 0xa3, 0x08, 0xff, 0x2d, 0x72, 0xb4, 0x06, 0x5c, 0x39, 0x8d, 0x9f, + 0x6c, 0x39, 0x9a, 0x98, 0xad, 0x27, 0x37, 0x52, 0x87, 0x33, 0x3e, 0xfe, 0xe6, 0x4c, 0xad, 0xe6, + 0xd8, 0xef, 0xd1, 0xa6, 0x57, 0xad, 0x88, 0x3d, 0x37, 0x86, 0x85, 0x34, 0x56, 0x1b, 0x6b, 0xcd, + 0x0e, 0x53, 0x0a, 0x86, 0x8b, 0x32, 0x4f, 0x2d, 0x4c, 0xee, 0xc2, 0x59, 0x09, 0x2e, 0x65, 0x82, + 0x87, 0xf0, 0x50, 0x40, 0x70, 0x4d, 0x4f, 0x06, 0x9f, 0x5e, 0x9c, 0xbc, 0x09, 0xa3, 0x3e, 0x82, + 0x5f, 0x45, 0x0e, 0xe3, 0x55, 0x24, 0x0e, 0x49, 0x63, 0xb5, 0x11, 0x7f, 0xc8, 0x1c, 0x25, 0x96, + 0x35, 0x6a, 0x65, 0xb3, 0x43, 0x85, 0x5b, 0xb0, 0xaf, 0x51, 0xde, 0x66, 0x27, 0x55, 0x19, 0x19, + 0x29, 0x79, 0x3b, 0xd4, 0xa8, 0x65, 0xc7, 0x5c, 0x33, 0xf9, 0x76, 0xdc, 0x7f, 0xbb, 0xbc, 0xda, + 0xb0, 0x11, 0x98, 0xa6, 0x1f, 0x9c, 0xfc, 0x62, 0x19, 0x4e, 0xa7, 0xe8, 0xd8, 0x9e, 0x76, 0x8c, + 0x5f, 0xcc, 0x87, 0x8d, 0x38, 0xe6, 0xdb, 0xc6, 0x69, 0x18, 0xf4, 0xbf, 0x44, 0x18, 0x0f, 0x4a, + 0xd6, 0xd0, 0x8c, 0xf3, 0xf0, 0xf1, 0x11, 0x71, 0x1c, 0xf3, 0xad, 0xe4, 0x61, 0x88, 0xe3, 0xdb, + 0xb9, 0x50, 0x1c, 0xc7, 0x7c, 0x7b, 0xf9, 0xe3, 0xc5, 0x70, 0x4e, 0x3a, 0xd9, 0x63, 0x1e, 0x96, + 0x99, 0x1c, 0x3a, 0xd3, 0x96, 0xf6, 0xf0, 0x86, 0x58, 0x56, 0xcd, 0x81, 0xfd, 0xa9, 0x26, 0x79, + 0x13, 0x86, 0x6b, 0xb6, 0xeb, 0xad, 0x39, 0xd4, 0xad, 0x05, 0xe9, 0x2b, 0xf0, 0xfd, 0x79, 0x47, + 0x80, 0x1b, 0x9d, 0xc8, 0xec, 0x2f, 0x93, 0xab, 0xff, 0xb8, 0x90, 0xd0, 0x06, 0x6e, 0xb8, 0x1e, + 0x4b, 0x6d, 0x38, 0x84, 0xa1, 0x4e, 0xae, 0x87, 0xab, 0x20, 0xb7, 0xf0, 0xfb, 0xa5, 0xd8, 0x9c, + 0xab, 0xc2, 0xc0, 0x8f, 0x92, 0x90, 0xef, 0x83, 0xf3, 0x11, 0x40, 0x4d, 0x77, 0xf4, 0x36, 0xf5, + 0xc2, 0x54, 0xa1, 0x18, 0x6d, 0xcd, 0x2f, 0xdd, 0xe8, 0x04, 0x68, 0x39, 0xfd, 0x68, 0x06, 0x07, + 0x49, 0xb5, 0x06, 0xf6, 0xe0, 0xa7, 0xfd, 0xd5, 0x42, 0x68, 0xe8, 0x44, 0xa3, 0x26, 0x6b, 0xd4, + 0xed, 0xb6, 0xbc, 0xc7, 0xb7, 0x83, 0xf7, 0x97, 0x93, 0x66, 0x1e, 0xc6, 0xcb, 0xf7, 0xef, 0xd3, + 0xa6, 0xe7, 0x07, 0x83, 0x77, 0x45, 0x9c, 0x4c, 0xbe, 0xf1, 0x10, 0x28, 0x11, 0xdc, 0x5b, 0xee, + 0xd7, 0x78, 0x31, 0xf5, 0x9f, 0x14, 0x41, 0x09, 0x0c, 0xff, 0xe0, 0xbd, 0xe2, 0x11, 0x2e, 0xb2, + 0x1f, 0x8a, 0x5e, 0x31, 0xe1, 0x54, 0x28, 0x0c, 0xf1, 0x50, 0x4c, 0x24, 0xaf, 0x9f, 0x8c, 0x33, + 0x0b, 0x09, 0xf9, 0x5e, 0xe2, 0xa2, 0xd8, 0x4b, 0x90, 0xf0, 0x3d, 0x68, 0xc3, 0xe5, 0x2c, 0xb4, + 0x24, 0x57, 0xf2, 0xa5, 0x1c, 0x9c, 0xf1, 0x3b, 0x65, 0x79, 0x95, 0x19, 0xd5, 0x33, 0x76, 0xd7, + 0x0a, 0x5e, 0x51, 0xbd, 0x9e, 0x5d, 0x1d, 0xef, 0xa4, 0xa9, 0xb4, 0xc2, 0xbc, 0x25, 0x41, 0x44, + 0x98, 0x40, 0x21, 0x6c, 0xa4, 0x69, 0x34, 0x91, 0x48, 0x4b, 0xad, 0xf7, 0xe2, 0x4d, 0xb8, 0x90, + 0xc9, 0x72, 0x27, 0x23, 0xb6, 0x5f, 0x36, 0x62, 0xff, 0xdb, 0x5c, 0x38, 0x11, 0xc5, 0x84, 0x44, + 0xa6, 0x00, 0x42, 0x90, 0xd8, 0xd6, 0xe2, 0x23, 0xad, 0x50, 0x68, 0x9a, 0x44, 0x41, 0x96, 0xa1, + 0x24, 0xc4, 0xc2, 0xd3, 0x72, 0x7f, 0x7c, 0x87, 0x5e, 0x98, 0x92, 0xe5, 0x80, 0x5b, 0x56, 0xf1, + 0xcd, 0x82, 0xcd, 0xc5, 0xd7, 0x60, 0x78, 0xbf, 0xdf, 0xf5, 0xa5, 0x02, 0x10, 0x79, 0x0f, 0x7a, + 0x84, 0x06, 0xfa, 0x31, 0x9e, 0xc2, 0xae, 0xc0, 0x20, 0xfb, 0x04, 0x4c, 0x54, 0x23, 0x05, 0xa6, + 0xee, 0x0a, 0x98, 0x16, 0x60, 0xc3, 0xa8, 0x70, 0x03, 0xe9, 0x51, 0xe1, 0xd4, 0x9f, 0x2a, 0xc0, + 0x39, 0xb9, 0x43, 0x2a, 0x14, 0x73, 0x5d, 0x9c, 0x74, 0xca, 0x07, 0xd8, 0x29, 0x2a, 0x94, 0xf8, + 0xd6, 0x43, 0x24, 0x1d, 0xe1, 0xc7, 0x42, 0x08, 0xd1, 0x04, 0x46, 0xfd, 0x9f, 0xf3, 0x30, 0x1a, + 0x98, 0x77, 0xba, 0xe3, 0x3e, 0xc6, 0xdd, 0xf1, 0x49, 0x18, 0xc5, 0xb8, 0x5e, 0x6d, 0x6a, 0xf1, + 0xd8, 0x57, 0xfd, 0x52, 0x96, 0x20, 0x1f, 0x21, 0x12, 0xc2, 0x45, 0x08, 0x99, 0xf6, 0x73, 0xcb, + 0x4f, 0x8a, 0xb6, 0xc6, 0xcd, 0x3e, 0x0e, 0x57, 0xff, 0x6a, 0x01, 0x46, 0x7c, 0x29, 0x4f, 0x9b, + 0xc7, 0xf5, 0x9e, 0xe7, 0x68, 0x85, 0x7c, 0x0d, 0xa0, 0x66, 0x3b, 0x9e, 0xde, 0x5a, 0x0a, 0x35, + 0x1f, 0x0f, 0x48, 0x3b, 0x08, 0xe5, 0x65, 0x24, 0x12, 0x5c, 0xbf, 0x42, 0xb3, 0x9a, 0x4f, 0x4c, + 0x7c, 0xfd, 0x0a, 0xa0, 0x9a, 0x44, 0xa1, 0xfe, 0x66, 0x1e, 0xc6, 0xfd, 0x4e, 0x9a, 0x7d, 0x44, + 0x9b, 0xdd, 0xc7, 0x79, 0x6e, 0x8a, 0x4a, 0xbb, 0x7f, 0x47, 0x69, 0xab, 0xff, 0x97, 0x34, 0x91, + 0xcc, 0xb4, 0xec, 0x93, 0x89, 0xe4, 0x5f, 0x87, 0x8e, 0xab, 0x3f, 0x5c, 0x80, 0x33, 0xbe, 0xd4, + 0xe7, 0xba, 0x16, 0x1e, 0x2d, 0xcc, 0xe8, 0xad, 0xd6, 0xe3, 0xbc, 0x1b, 0x1f, 0xf6, 0x05, 0xb1, + 0x2c, 0x02, 0x65, 0x8a, 0xe4, 0x9c, 0xf7, 0x05, 0xb8, 0x61, 0x9b, 0x86, 0x26, 0x13, 0x91, 0xb7, + 0x61, 0xc4, 0xff, 0x59, 0x76, 0xd6, 0xfc, 0x2d, 0x38, 0x5e, 0x14, 0x04, 0x85, 0x74, 0x27, 0x12, + 0x5d, 0x23, 0x52, 0x40, 0xfd, 0xc2, 0x00, 0x5c, 0xbc, 0x67, 0x5a, 0x86, 0xbd, 0xe1, 0xfa, 0xb9, + 0x5d, 0x8f, 0xfd, 0x41, 0xd9, 0x51, 0xe7, 0x74, 0x7d, 0x07, 0xce, 0xc6, 0x45, 0xea, 0x04, 0x11, + 0xf7, 0x45, 0xef, 0x6c, 0x70, 0x82, 0x86, 0x9f, 0xe5, 0x55, 0xdc, 0xb6, 0x69, 0xe9, 0x25, 0xe3, + 0x69, 0x62, 0x07, 0x76, 0x93, 0x26, 0xf6, 0x39, 0x28, 0x55, 0xec, 0xb6, 0x6e, 0xfa, 0x71, 0x96, + 0x70, 0x14, 0x07, 0xf5, 0x22, 0x46, 0x13, 0x14, 0x8c, 0xbf, 0xa8, 0x18, 0xbb, 0x6c, 0x28, 0xe4, + 0xef, 0x17, 0x60, 0x56, 0x9a, 0x26, 0x13, 0x11, 0x1b, 0x46, 0x45, 0x75, 0xe2, 0x6e, 0x0c, 0x70, + 0xf3, 0xf4, 0xb2, 0x2f, 0xa3, 0x6c, 0xb5, 0x9a, 0x8a, 0x94, 0xe3, 0xdb, 0x28, 0x9e, 0xbd, 0x56, + 0x7c, 0x0c, 0xbf, 0x25, 0xd3, 0xa2, 0xfc, 0x25, 0x21, 0xe0, 0x24, 0x33, 0x9c, 0x14, 0x02, 0xce, + 0x32, 0x32, 0x11, 0x99, 0x85, 0x53, 0x18, 0x17, 0x3d, 0xd8, 0x4a, 0x31, 0x95, 0x18, 0x41, 0xa3, + 0x12, 0xaf, 0x5c, 0x78, 0x28, 0x75, 0xf6, 0x71, 0x8d, 0xa6, 0x40, 0x6b, 0xc9, 0x12, 0xe4, 0x02, + 0x14, 0x96, 0x16, 0xca, 0x78, 0x57, 0x33, 0xc8, 0x73, 0x92, 0x59, 0x2d, 0x5d, 0x63, 0xb0, 0x8b, + 0x9f, 0x06, 0x92, 0xfc, 0x9c, 0x3d, 0xdd, 0xc7, 0xfc, 0x7d, 0x69, 0xcb, 0x77, 0xdc, 0x3d, 0x6a, + 0x0e, 0x63, 0x22, 0x8c, 0xa4, 0x03, 0xec, 0xff, 0x20, 0xd3, 0x01, 0x96, 0x0e, 0x35, 0x1d, 0xa0, + 0xfa, 0x4b, 0x39, 0x38, 0x95, 0xc8, 0x1d, 0x40, 0x5e, 0x02, 0xe0, 0x10, 0x29, 0x46, 0x2b, 0x86, + 0x10, 0x0a, 0xf3, 0x09, 0x88, 0xe5, 0x31, 0x24, 0x23, 0xd7, 0x60, 0x90, 0xff, 0x12, 0x51, 0xca, + 0x92, 0x45, 0xba, 0x5d, 0xd3, 0xd0, 0x02, 0xa2, 0xb0, 0x16, 0xbc, 0x91, 0x2c, 0xa4, 0x16, 0xf1, + 0x36, 0x3b, 0x41, 0x2d, 0x8c, 0x4c, 0xfd, 0x89, 0x3c, 0x8c, 0x04, 0x0d, 0x2e, 0x1b, 0x47, 0xa5, + 0x73, 0x25, 0x91, 0x86, 0xa1, 0xb0, 0x53, 0x1a, 0x86, 0xd8, 0x7c, 0x2b, 0xf2, 0x2e, 0x1c, 0xde, + 0xab, 0xac, 0x2f, 0xe7, 0x61, 0x3c, 0xa8, 0xf5, 0x08, 0x2f, 0xbf, 0x3e, 0x44, 0x22, 0xf9, 0x52, + 0x0e, 0x94, 0x69, 0xb3, 0xd5, 0x32, 0xad, 0xb5, 0xaa, 0x75, 0xdf, 0x76, 0xda, 0x38, 0x21, 0x1e, + 0xdd, 0x11, 0xae, 0xfa, 0xe7, 0x73, 0x70, 0x4a, 0x34, 0x68, 0x46, 0x77, 0x8c, 0xa3, 0x3b, 0x1f, + 0x8b, 0xb7, 0xe4, 0xe8, 0xf4, 0x45, 0xfd, 0x46, 0x1e, 0x60, 0xc1, 0x6e, 0x3e, 0x38, 0xe6, 0x8f, + 0xba, 0xde, 0x80, 0x12, 0x77, 0x8b, 0x17, 0x1a, 0x7b, 0x4a, 0x3c, 0x5e, 0x62, 0x9f, 0xc6, 0x11, + 0xd3, 0x13, 0x62, 0x3e, 0x2e, 0x71, 0xcf, 0x7a, 0x25, 0xa7, 0x89, 0x22, 0xac, 0x52, 0x46, 0x27, + 0x16, 0x8c, 0xa0, 0x52, 0x06, 0x8b, 0x56, 0xba, 0xbd, 0x35, 0x59, 0x6c, 0xd9, 0xcd, 0x07, 0x1a, + 0xd2, 0xab, 0xff, 0x32, 0xc7, 0x65, 0x77, 0xcc, 0x9f, 0xa6, 0xfa, 0x9f, 0x5f, 0xdc, 0xe3, 0xe7, + 0xff, 0x85, 0x1c, 0x9c, 0xd1, 0x68, 0xd3, 0x7e, 0x48, 0x9d, 0xcd, 0x19, 0xdb, 0xa0, 0x37, 0xa9, + 0x45, 0x9d, 0xa3, 0x1a, 0x51, 0xbf, 0x85, 0x79, 0x6b, 0xc2, 0xc6, 0xdc, 0x71, 0xa9, 0x71, 0x7c, + 0x72, 0x0a, 0xa9, 0xbf, 0x32, 0x00, 0x4a, 0xaa, 0xd5, 0x7b, 0x6c, 0xcd, 0xb9, 0xcc, 0xad, 0x4c, + 0xf1, 0xb0, 0xb6, 0x32, 0xfd, 0x7b, 0xdb, 0xca, 0x94, 0xf6, 0xba, 0x95, 0x19, 0xd8, 0xcd, 0x56, + 0xa6, 0x1d, 0xdf, 0xca, 0x0c, 0xe2, 0x56, 0xe6, 0xa5, 0x9e, 0x5b, 0x99, 0x59, 0xcb, 0xd8, 0xe7, + 0x46, 0xe6, 0xd8, 0xe6, 0xbb, 0xde, 0xcf, 0x0e, 0xec, 0x0a, 0x9b, 0x14, 0x9b, 0xb6, 0x63, 0x50, + 0x43, 0x6c, 0xbc, 0xf0, 0xd4, 0xdf, 0x11, 0x30, 0x2d, 0xc0, 0x26, 0x92, 0x87, 0x8f, 0xee, 0x26, + 0x79, 0xf8, 0x21, 0xec, 0xbf, 0xbe, 0x98, 0x87, 0x53, 0x33, 0xd4, 0xf1, 0x78, 0x2c, 0xda, 0xc3, + 0x70, 0x89, 0x2b, 0xc3, 0xb8, 0xc4, 0x10, 0x2d, 0xf2, 0x7c, 0xe8, 0xe6, 0xd7, 0xa4, 0x8e, 0x17, + 0xf7, 0x12, 0x8c, 0xd3, 0xb3, 0xea, 0xfd, 0x04, 0x7e, 0x62, 0xec, 0x06, 0xd5, 0xfb, 0x70, 0x2e, + 0x48, 0x53, 0xfc, 0xd2, 0x02, 0x7a, 0x29, 0x27, 0x5f, 0x71, 0xef, 0x39, 0xf9, 0xd4, 0x5f, 0xcc, + 0xc1, 0x65, 0x8d, 0x5a, 0x74, 0x43, 0x5f, 0x6d, 0x51, 0xa9, 0x59, 0x62, 0x65, 0x60, 0xb3, 0x86, + 0xe9, 0xb6, 0x75, 0xaf, 0xb9, 0x7e, 0x20, 0x19, 0xcd, 0xc1, 0x88, 0x3c, 0x7f, 0xed, 0x61, 0x6e, + 0x8b, 0x94, 0x53, 0x7f, 0xa5, 0x08, 0x03, 0xd3, 0xb6, 0x77, 0xcb, 0x3e, 0x60, 0x92, 0xc8, 0x70, + 0xca, 0xcf, 0xef, 0xe1, 0xac, 0xe7, 0x13, 0x58, 0xb9, 0x94, 0x37, 0x03, 0x5d, 0x48, 0x57, 0xed, + 0x44, 0x7e, 0x11, 0x9f, 0x6c, 0x8f, 0xe9, 0x21, 0x5f, 0x81, 0x21, 0x0c, 0x21, 0x23, 0x9d, 0xc6, + 0xa2, 0x83, 0xb6, 0xc7, 0x80, 0xf1, 0x3a, 0x42, 0x52, 0xf2, 0x7d, 0x91, 0xe0, 0xb9, 0xa5, 0x83, + 0xa7, 0x93, 0x94, 0xe3, 0xe8, 0xbe, 0xc4, 0x2f, 0xf2, 0xb0, 0x4d, 0x52, 0xea, 0x1d, 0x3c, 0x45, + 0x89, 0x35, 0x29, 0x20, 0x3c, 0xc4, 0x54, 0x8f, 0x33, 0x30, 0x3a, 0x6d, 0x7b, 0x92, 0x33, 0xf0, + 0x50, 0xf8, 0x96, 0x94, 0x49, 0x3e, 0xdd, 0x13, 0x38, 0x5a, 0x46, 0xfd, 0x93, 0x22, 0x8c, 0xf8, + 0x3f, 0x8f, 0x48, 0x77, 0x5e, 0x80, 0xd2, 0xbc, 0x2d, 0x65, 0x1f, 0x41, 0x07, 0xe2, 0x75, 0xdb, + 0x8d, 0x79, 0x46, 0x0b, 0x22, 0x26, 0xf5, 0x25, 0xdb, 0x90, 0xdd, 0xdf, 0x51, 0xea, 0x96, 0x6d, + 0x24, 0xde, 0x20, 0x07, 0x84, 0xe4, 0x32, 0x14, 0xf1, 0xe5, 0x80, 0x74, 0x90, 0x1f, 0x7b, 0x2d, + 0x80, 0x78, 0x49, 0x2b, 0x4b, 0x7b, 0xd5, 0xca, 0x81, 0xfd, 0x6a, 0xe5, 0xe0, 0xe1, 0x6a, 0xe5, + 0xbb, 0x30, 0x82, 0x35, 0xf9, 0xc9, 0x0b, 0x77, 0x5e, 0x58, 0x2f, 0x88, 0xb5, 0x6f, 0x94, 0xb7, + 0x5b, 0xa4, 0x30, 0xc4, 0x25, 0x2f, 0xc2, 0x2a, 0xa6, 0xbb, 0x70, 0x80, 0xed, 0xf4, 0x3f, 0xce, + 0xc1, 0xc0, 0x1d, 0xeb, 0x81, 0x65, 0x6f, 0x1c, 0x4c, 0xe3, 0x5e, 0x82, 0x61, 0xc1, 0x46, 0x5a, + 0x5d, 0xf0, 0x59, 0x79, 0x97, 0x83, 0x1b, 0xc8, 0x49, 0x93, 0xa9, 0xc8, 0x9b, 0x41, 0x21, 0x7c, + 0x1c, 0x54, 0x08, 0xf3, 0xf7, 0xf8, 0x85, 0x9a, 0xd1, 0x04, 0x1e, 0x32, 0x39, 0xb9, 0x04, 0xc5, + 0x0a, 0x6b, 0xaa, 0x14, 0xc8, 0x97, 0x35, 0x45, 0x43, 0xa8, 0xfa, 0xc5, 0x22, 0x8c, 0xc5, 0x0e, + 0xbe, 0x9e, 0x83, 0x21, 0x71, 0xf0, 0x64, 0xfa, 0x19, 0x45, 0xf0, 0xf1, 0x50, 0x00, 0xd4, 0x06, + 0xf9, 0x9f, 0x55, 0x83, 0x7c, 0x0a, 0x06, 0x6c, 0x17, 0x17, 0x45, 0xfc, 0x96, 0xb1, 0x70, 0x08, + 0x2d, 0xd7, 0x59, 0xdb, 0xf9, 0xe0, 0x10, 0x24, 0xb2, 0x46, 0xda, 0x2e, 0x7e, 0xda, 0x0d, 0x18, + 0xd2, 0x5d, 0x97, 0x7a, 0x0d, 0x4f, 0x5f, 0x93, 0x93, 0x8c, 0x04, 0x40, 0x79, 0x74, 0x20, 0x70, + 0x45, 0x5f, 0x23, 0x9f, 0x86, 0xd1, 0xa6, 0x43, 0x71, 0xd9, 0xd4, 0x5b, 0xac, 0x95, 0x92, 0x59, + 0x1b, 0x41, 0xc8, 0xf7, 0x27, 0x21, 0xa2, 0x6a, 0x90, 0xbb, 0x30, 0x2a, 0x3e, 0x87, 0x7b, 0xee, + 0xe3, 0x40, 0x1b, 0x0b, 0x97, 0x31, 0x2e, 0x12, 0xee, 0xbb, 0x2f, 0x1e, 0x70, 0xc8, 0xe4, 0x32, + 0x5f, 0x43, 0x22, 0x25, 0xcb, 0x40, 0x36, 0xe8, 0x6a, 0x43, 0xef, 0x7a, 0xeb, 0xac, 0x2e, 0x1e, + 0x23, 0x5f, 0x64, 0x03, 0xc5, 0x57, 0x0f, 0x49, 0xac, 0xfc, 0x18, 0x64, 0x83, 0xae, 0x96, 0x23, + 0x48, 0x72, 0x0f, 0xce, 0x26, 0x8b, 0xb0, 0x4f, 0xe6, 0x97, 0x03, 0xcf, 0x6e, 0x6f, 0x4d, 0x4e, + 0xa6, 0x12, 0x48, 0x6c, 0x4f, 0x27, 0xd8, 0x56, 0x8d, 0x5b, 0xc5, 0xc1, 0x81, 0x89, 0x41, 0x6d, + 0x8c, 0x95, 0xf5, 0x4d, 0x48, 0xd3, 0x50, 0x7f, 0x2f, 0xc7, 0x4c, 0x45, 0xf6, 0x41, 0x98, 0x0e, + 0x9d, 0xe9, 0x7a, 0x7b, 0x8f, 0xba, 0xde, 0x0e, 0x13, 0x97, 0x96, 0xdc, 0x1e, 0xb3, 0xab, 0x26, + 0xb0, 0x64, 0x0a, 0x4a, 0x86, 0x7c, 0x6a, 0x76, 0x2e, 0xda, 0x09, 0x7e, 0x3d, 0x9a, 0xa0, 0x22, + 0x57, 0xa0, 0xc8, 0x96, 0xac, 0xf8, 0x96, 0x59, 0xb6, 0x2e, 0x34, 0xa4, 0x50, 0x7f, 0x28, 0x0f, + 0x23, 0xd2, 0xd7, 0x5c, 0x3f, 0xd0, 0xe7, 0xbc, 0xbe, 0xbb, 0x66, 0xfa, 0x4e, 0x2f, 0xb8, 0x97, + 0xf2, 0x9b, 0x7c, 0x23, 0x10, 0xc5, 0xae, 0x2e, 0xa4, 0x84, 0x60, 0x5e, 0x11, 0x1f, 0x5a, 0xda, + 0xfd, 0xf6, 0x91, 0xd1, 0xdf, 0x2a, 0x0e, 0xe6, 0x27, 0x0a, 0xb7, 0x8a, 0x83, 0xc5, 0x89, 0x7e, + 0x0c, 0xe6, 0x85, 0xf1, 0xb3, 0xf9, 0xde, 0xdc, 0xba, 0x6f, 0xae, 0x1d, 0xf3, 0xb7, 0x23, 0x87, + 0x1b, 0xe8, 0x2c, 0x26, 0x9b, 0x63, 0xfe, 0x90, 0xe4, 0x03, 0x95, 0xcd, 0x49, 0xa2, 0x53, 0x21, + 0x9b, 0x7f, 0x92, 0x03, 0x25, 0x55, 0x36, 0xe5, 0x23, 0xf2, 0x83, 0x38, 0xbc, 0x74, 0xa7, 0x7f, + 0x94, 0x87, 0x53, 0x55, 0xcb, 0xa3, 0x6b, 0x7c, 0xc7, 0x78, 0xcc, 0xa7, 0x8a, 0xdb, 0x30, 0x2c, + 0x7d, 0x8c, 0xe8, 0xf3, 0x27, 0x82, 0xfd, 0x78, 0x88, 0xca, 0xe0, 0x24, 0x97, 0x3e, 0xbc, 0x97, + 0x38, 0x71, 0x21, 0x1f, 0xf3, 0x39, 0xe7, 0x78, 0x08, 0xf9, 0x98, 0x4f, 0x5e, 0x1f, 0x52, 0x21, + 0xff, 0x1f, 0x39, 0x38, 0x9d, 0x52, 0x39, 0xb9, 0x0c, 0x03, 0xf5, 0xee, 0x2a, 0xc6, 0xee, 0xca, + 0x85, 0x1e, 0xc3, 0x6e, 0x77, 0x15, 0xc3, 0x76, 0x69, 0x3e, 0x92, 0xac, 0xe0, 0xe3, 0xfa, 0xe5, + 0x6a, 0x65, 0x46, 0x48, 0x55, 0x95, 0xc2, 0x04, 0x30, 0x70, 0xda, 0x97, 0x05, 0x0f, 0xf0, 0x6d, + 0xd3, 0x68, 0xc6, 0x1e, 0xe0, 0xb3, 0x32, 0xe4, 0xfb, 0x61, 0xa8, 0xfc, 0x7e, 0xd7, 0xa1, 0xc8, + 0x97, 0x4b, 0xfc, 0x23, 0x01, 0x5f, 0x1f, 0x91, 0xc6, 0x99, 0xc7, 0x12, 0x60, 0x14, 0x71, 0xde, + 0x21, 0x43, 0xf5, 0x27, 0x72, 0x70, 0x31, 0xbb, 0x75, 0xe4, 0x13, 0x30, 0xc0, 0x76, 0xe6, 0x65, + 0x6d, 0x49, 0x7c, 0x3a, 0x4f, 0x0d, 0x6c, 0xb7, 0x68, 0x43, 0x77, 0x64, 0x63, 0xdf, 0x27, 0x23, + 0x6f, 0xc1, 0x70, 0xd5, 0x75, 0xbb, 0xd4, 0xa9, 0xbf, 0x74, 0x47, 0xab, 0x8a, 0x3d, 0x21, 0xee, + 0x39, 0x4c, 0x04, 0x37, 0xdc, 0x97, 0x62, 0xd1, 0xb9, 0x64, 0x7a, 0xf5, 0x47, 0x73, 0x70, 0xa9, + 0xd7, 0x57, 0x91, 0x97, 0x60, 0x70, 0x85, 0x5a, 0xba, 0xe5, 0x55, 0x2b, 0xa2, 0x49, 0xb8, 0xc5, + 0xf2, 0x10, 0x16, 0xdd, 0x29, 0x04, 0x84, 0xac, 0x10, 0x3f, 0x57, 0x0c, 0x1c, 0x19, 0xf8, 0x19, + 0x28, 0xc2, 0x62, 0x85, 0x7c, 0x42, 0xf5, 0xf7, 0xf3, 0x30, 0x52, 0x6b, 0x75, 0xd7, 0x4c, 0x69, + 0xe1, 0xd8, 0xb7, 0xbd, 0xed, 0x5b, 0xbf, 0xf9, 0xbd, 0x59, 0xbf, 0x6c, 0xb8, 0x39, 0xfb, 0x1c, + 0x6e, 0x7e, 0x39, 0xf2, 0x26, 0x94, 0x3a, 0xf8, 0x1d, 0xf1, 0x93, 0x58, 0xfe, 0x75, 0x59, 0x27, + 0xb1, 0xbc, 0x0c, 0x1b, 0x5f, 0xcd, 0x03, 0x8c, 0xaf, 0xb0, 0xac, 0x24, 0xd0, 0x70, 0x91, 0x38, + 0x11, 0xe8, 0xa1, 0x08, 0x34, 0x5c, 0x10, 0x4e, 0x04, 0x7a, 0x00, 0x81, 0xfe, 0x4a, 0x1e, 0xc6, + 0xa2, 0x55, 0x92, 0x4f, 0xc0, 0x30, 0xaf, 0x86, 0x9f, 0x0b, 0xe5, 0x24, 0xa7, 0xe2, 0x10, 0xac, + 0x01, 0xff, 0x21, 0x0e, 0xb8, 0xc6, 0xd7, 0x75, 0xb7, 0x11, 0x9e, 0xd0, 0xf0, 0xfb, 0xdb, 0x41, + 0xee, 0x09, 0x15, 0x43, 0x69, 0x63, 0xeb, 0xba, 0x3b, 0x13, 0xfe, 0x26, 0xb3, 0x40, 0x1c, 0xda, + 0x75, 0x69, 0x94, 0x41, 0x11, 0x19, 0x88, 0xbc, 0xec, 0x71, 0xac, 0x76, 0x8a, 0xc3, 0x64, 0x36, + 0x9f, 0x0b, 0x9a, 0x8d, 0xca, 0xd0, 0xbf, 0x8b, 0xa4, 0xf1, 0x12, 0x7d, 0xfa, 0x31, 0x27, 0x27, + 0xa8, 0xe8, 0x9e, 0xce, 0x37, 0xe5, 0x7e, 0x07, 0xa8, 0x7f, 0xea, 0x42, 0xff, 0xb2, 0x45, 0x97, + 0xef, 0x93, 0x17, 0x61, 0x88, 0x29, 0xcc, 0x82, 0xcd, 0xfa, 0x32, 0x27, 0xfc, 0x27, 0x24, 0x4d, + 0x42, 0xc4, 0x7c, 0x9f, 0x16, 0x52, 0x91, 0x1b, 0x00, 0xe1, 0x13, 0x33, 0xa1, 0x7d, 0x44, 0x2e, + 0xc3, 0x31, 0xf3, 0x7d, 0x9a, 0x44, 0xe7, 0x97, 0x12, 0x0f, 0x74, 0x0a, 0xc9, 0x52, 0x1c, 0xe3, + 0x97, 0x12, 0xe3, 0x63, 0x01, 0x08, 0xfb, 0x55, 0xd3, 0x5d, 0x77, 0xc3, 0x76, 0x8c, 0x99, 0x75, + 0xdd, 0x5a, 0xa3, 0xf1, 0xdd, 0x53, 0x92, 0x62, 0xbe, 0x4f, 0x4b, 0x29, 0x47, 0x5e, 0x87, 0x11, + 0xd9, 0xa1, 0x34, 0xee, 0xf4, 0x21, 0xe3, 0xe6, 0xfb, 0xb4, 0x08, 0x2d, 0x79, 0x15, 0x86, 0xc5, + 0xef, 0x5b, 0xb6, 0xb8, 0x51, 0x96, 0x62, 0x11, 0x49, 0xa8, 0xf9, 0x3e, 0x4d, 0xa6, 0x94, 0x2a, + 0xad, 0x39, 0xa6, 0xe5, 0x89, 0x37, 0xca, 0xf1, 0x4a, 0x11, 0x27, 0x55, 0x8a, 0xbf, 0xc9, 0x5b, + 0x30, 0x1a, 0x04, 0x79, 0x7a, 0x8f, 0x36, 0x3d, 0x71, 0xf8, 0x7d, 0x36, 0x56, 0x98, 0x23, 0xe7, + 0xfb, 0xb4, 0x28, 0x35, 0xb9, 0x02, 0x25, 0x8d, 0xba, 0xe6, 0xfb, 0xfe, 0x75, 0xf1, 0x98, 0x34, + 0xce, 0xcd, 0xf7, 0x99, 0x94, 0x04, 0x9e, 0xf5, 0x4e, 0x78, 0x3f, 0x2d, 0x8e, 0xaa, 0x49, 0xac, + 0x96, 0x59, 0xcb, 0x60, 0xbd, 0x23, 0x39, 0x27, 0x7c, 0x3a, 0x0c, 0x7d, 0x25, 0x32, 0xbf, 0x0e, + 0xc7, 0x63, 0x0c, 0xc8, 0xd8, 0xf9, 0x3e, 0x2d, 0x46, 0x2f, 0x49, 0xb5, 0x62, 0xba, 0x0f, 0x44, + 0xc8, 0xd2, 0xb8, 0x54, 0x19, 0x4a, 0x92, 0x2a, 0xfb, 0x29, 0x55, 0xbd, 0x44, 0xbd, 0x0d, 0xdb, + 0x79, 0x20, 0x02, 0x94, 0xc6, 0xab, 0x16, 0x58, 0xa9, 0x6a, 0x01, 0x91, 0xab, 0x66, 0x03, 0x6e, + 0x2c, 0xbd, 0x6a, 0xdd, 0xd3, 0xe5, 0xaa, 0xf9, 0x49, 0x9c, 0xdf, 0x49, 0x0b, 0x54, 0x7f, 0x48, + 0x95, 0xf1, 0xd4, 0x0e, 0x45, 0x9c, 0xd4, 0xa1, 0xf8, 0x9b, 0x55, 0x2a, 0xa5, 0x85, 0x57, 0x26, + 0xa2, 0x95, 0x4a, 0x28, 0x56, 0xa9, 0x9c, 0x40, 0xfe, 0x86, 0x9c, 0x7b, 0x5c, 0x39, 0x15, 0xed, + 0xa0, 0x10, 0xc3, 0x3a, 0x48, 0xca, 0x51, 0x3e, 0x89, 0x79, 0x8d, 0x15, 0x82, 0xe4, 0xc3, 0x41, + 0x0b, 0x67, 0x6a, 0xf3, 0x7d, 0x1a, 0x66, 0x3c, 0x56, 0x79, 0xc6, 0x6c, 0xe5, 0x34, 0x52, 0x8c, + 0xf8, 0x14, 0x0c, 0x36, 0xdf, 0xa7, 0xf1, 0x6c, 0xda, 0x2f, 0x4a, 0x59, 0x05, 0x95, 0x33, 0xd1, + 0x29, 0x22, 0x40, 0xb0, 0x29, 0x22, 0xcc, 0x3d, 0x38, 0x97, 0xcc, 0x9d, 0xa7, 0x9c, 0x8d, 0x2e, + 0x35, 0x71, 0xfc, 0x7c, 0x9f, 0x96, 0xcc, 0xb7, 0xf7, 0x6a, 0x24, 0x9d, 0x9c, 0x72, 0x2e, 0x16, + 0x00, 0x2c, 0x44, 0x31, 0x71, 0xc9, 0x89, 0xe7, 0x96, 0xe1, 0x34, 0xcf, 0x46, 0x2b, 0x42, 0x78, + 0x89, 0xc9, 0xea, 0x7c, 0x74, 0xe3, 0x92, 0x42, 0x32, 0xdf, 0xa7, 0xa5, 0x95, 0x24, 0x33, 0x89, + 0xa4, 0x6e, 0x8a, 0x12, 0xf5, 0x8d, 0x89, 0xa1, 0xe7, 0xfb, 0xb4, 0x44, 0x1a, 0xb8, 0x1b, 0x72, + 0x36, 0x35, 0xe5, 0x42, 0xb4, 0x13, 0x43, 0x0c, 0xeb, 0x44, 0x29, 0xeb, 0xda, 0x0d, 0x39, 0xc3, + 0x96, 0x72, 0x31, 0x59, 0x2a, 0x9c, 0x39, 0xa5, 0x4c, 0x5c, 0x5a, 0x7a, 0xd2, 0x20, 0xe5, 0x09, + 0x91, 0x3a, 0x58, 0x94, 0x4f, 0xa3, 0x99, 0xef, 0xd3, 0xd2, 0x13, 0x0e, 0x69, 0xe9, 0xd9, 0x76, + 0x94, 0x4b, 0xbd, 0x78, 0x06, 0xad, 0x4b, 0xcf, 0xd4, 0xa3, 0xf7, 0xc8, 0x7d, 0xa2, 0x3c, 0x19, + 0x0d, 0x61, 0x9c, 0x49, 0x38, 0xdf, 0xa7, 0xf5, 0xc8, 0xa0, 0x72, 0x27, 0x23, 0x11, 0x89, 0xf2, + 0x54, 0x34, 0x73, 0x78, 0x2a, 0xd1, 0x7c, 0x9f, 0x96, 0x91, 0xc6, 0xe4, 0x4e, 0x46, 0x9e, 0x0a, + 0x65, 0xb2, 0x27, 0xdb, 0x40, 0x1e, 0x19, 0x59, 0x2e, 0x96, 0x53, 0x53, 0x3c, 0x28, 0x4f, 0x47, + 0x55, 0x37, 0x85, 0x84, 0xa9, 0x6e, 0x5a, 0x72, 0x88, 0xe5, 0xd4, 0x9c, 0x04, 0xca, 0x33, 0x3d, + 0x18, 0x06, 0x6d, 0x4c, 0xcd, 0x66, 0xb0, 0x9c, 0x9a, 0x14, 0x40, 0x51, 0xa3, 0x0c, 0x53, 0x48, + 0x18, 0xc3, 0xb4, 0x74, 0x02, 0xcb, 0xa9, 0xb1, 0xe3, 0x95, 0x67, 0x7b, 0x30, 0x0c, 0x5b, 0x98, + 0x16, 0x75, 0xfe, 0xd5, 0x48, 0xf0, 0x76, 0xe5, 0x23, 0xd1, 0x79, 0x43, 0x42, 0xb1, 0x79, 0x43, + 0x0e, 0xf3, 0x3e, 0x93, 0x88, 0x2c, 0xab, 0x7c, 0x34, 0x3a, 0xcc, 0x63, 0x68, 0x36, 0xcc, 0xe3, + 0xb1, 0x68, 0x67, 0x12, 0x11, 0x36, 0x95, 0xcb, 0x59, 0x4c, 0x10, 0x1d, 0x65, 0xc2, 0x63, 0x72, + 0x56, 0x53, 0x42, 0x3c, 0x2a, 0x1f, 0x8b, 0xfa, 0x75, 0x27, 0x08, 0xe6, 0xfb, 0xb4, 0x94, 0xc0, + 0x90, 0x5a, 0x7a, 0x3c, 0x23, 0xe5, 0x4a, 0x74, 0xd8, 0xa6, 0xd1, 0xb0, 0x61, 0x9b, 0x1a, 0x0b, + 0x69, 0x21, 0xed, 0xf1, 0x89, 0x72, 0x35, 0x6a, 0x98, 0x25, 0x29, 0x98, 0x61, 0x96, 0xf2, 0x68, + 0x45, 0x4b, 0x8f, 0xb1, 0xa3, 0x3c, 0xd7, 0xb3, 0x85, 0x48, 0x93, 0xd2, 0x42, 0x1e, 0x72, 0x26, + 0xb4, 0x9d, 0xee, 0x74, 0x5a, 0xb6, 0x6e, 0x28, 0x1f, 0x4f, 0xb5, 0x9d, 0x38, 0x52, 0xb2, 0x9d, + 0x38, 0x80, 0xad, 0xf2, 0xf2, 0x1b, 0x07, 0xe5, 0xf9, 0xe8, 0x2a, 0x2f, 0xe3, 0xd8, 0x2a, 0x1f, + 0x79, 0x0f, 0x31, 0x93, 0x78, 0x0f, 0xa0, 0xbc, 0x10, 0x55, 0x80, 0x18, 0x9a, 0x29, 0x40, 0xfc, + 0x05, 0xc1, 0xe7, 0xb3, 0x3d, 0xe8, 0x95, 0x29, 0xe4, 0xf6, 0xb4, 0xcf, 0x2d, 0x8b, 0x6e, 0xbe, + 0x4f, 0xcb, 0xf6, 0xc2, 0xaf, 0xa6, 0x38, 0xc4, 0x2b, 0xd7, 0xa2, 0x0a, 0x96, 0x20, 0x60, 0x0a, + 0x96, 0x74, 0xa3, 0xaf, 0xa6, 0x78, 0xb4, 0x2b, 0x9f, 0xc8, 0x64, 0x15, 0x7c, 0x73, 0x8a, 0x1f, + 0xfc, 0x0d, 0xd9, 0x25, 0x5d, 0x79, 0x31, 0xba, 0xd8, 0x85, 0x18, 0xb6, 0xd8, 0x49, 0xae, 0xeb, + 0x37, 0x64, 0x67, 0x6c, 0xe5, 0x7a, 0xb2, 0x54, 0xb8, 0x44, 0x4a, 0x4e, 0xdb, 0x5a, 0xba, 0x0f, + 0xb3, 0xf2, 0x52, 0x54, 0xeb, 0xd2, 0x68, 0x98, 0xd6, 0xa5, 0xfa, 0x3f, 0xcf, 0x25, 0x5d, 0x91, + 0x95, 0x1b, 0xf1, 0x4d, 0x76, 0x14, 0xcf, 0x2c, 0x9f, 0x84, 0xfb, 0xf2, 0xa7, 0xe3, 0xc1, 0xf6, + 0x94, 0x97, 0x63, 0xd7, 0xbe, 0x11, 0x2c, 0xb3, 0x6f, 0x63, 0xc1, 0xf9, 0x3e, 0x1d, 0x8f, 0x4f, + 0xa7, 0xbc, 0x92, 0xce, 0x21, 0xd0, 0x95, 0x78, 0x3c, 0xbb, 0x4f, 0xc7, 0x43, 0xba, 0x29, 0xaf, + 0xa6, 0x73, 0x08, 0xa4, 0x1b, 0x0f, 0x01, 0xf7, 0xa2, 0x14, 0x64, 0x5e, 0xf9, 0x64, 0xd4, 0x74, + 0x0c, 0x10, 0xcc, 0x74, 0x0c, 0x43, 0xd1, 0xbf, 0x28, 0x05, 0x67, 0x57, 0x5e, 0x4b, 0x14, 0x09, + 0x1a, 0x2b, 0x85, 0x70, 0x7f, 0x51, 0x0a, 0x6a, 0xae, 0xbc, 0x9e, 0x28, 0x12, 0xb4, 0x4e, 0x0a, + 0x7d, 0x6e, 0xf4, 0x7a, 0xbf, 0xaa, 0xbc, 0x11, 0x3d, 0x0c, 0xce, 0xa6, 0x9c, 0xef, 0xd3, 0x7a, + 0xbd, 0x83, 0xfd, 0x7c, 0xb6, 0x63, 0xb7, 0xf2, 0x66, 0x74, 0x08, 0x67, 0xd1, 0xb1, 0x21, 0x9c, + 0xe9, 0x1c, 0xfe, 0x56, 0x2c, 0x96, 0x85, 0xf2, 0x56, 0x74, 0x8a, 0x8b, 0x20, 0xd9, 0x14, 0x17, + 0x8f, 0x7c, 0x11, 0x09, 0xd2, 0xa0, 0x7c, 0x2a, 0x3a, 0xc5, 0xc9, 0x38, 0x36, 0xc5, 0x45, 0x02, + 0x3a, 0xcc, 0x24, 0x62, 0x07, 0x28, 0x6f, 0x47, 0xa7, 0xb8, 0x18, 0x9a, 0x4d, 0x71, 0xf1, 0x68, + 0x03, 0x6f, 0xc5, 0x9e, 0xd0, 0x2b, 0x9f, 0x4e, 0x6f, 0x3f, 0x22, 0xe5, 0xf6, 0xf3, 0x07, 0xf7, + 0x5a, 0xfa, 0x5b, 0x70, 0xa5, 0x1c, 0x1d, 0xbf, 0x69, 0x34, 0x6c, 0xfc, 0xa6, 0xbe, 0x23, 0x8f, + 0x6f, 0x1c, 0x84, 0x56, 0x4d, 0xf7, 0xd8, 0x38, 0x84, 0xa6, 0x48, 0x0a, 0x38, 0xb2, 0x47, 0xe6, + 0x1b, 0xa1, 0x99, 0x8c, 0x3d, 0xb2, 0xbf, 0x0d, 0x8a, 0xd1, 0xb3, 0xd9, 0x35, 0xe1, 0x67, 0xac, + 0x54, 0xa2, 0xb3, 0x6b, 0x82, 0x80, 0xcd, 0xae, 0x49, 0xef, 0xe4, 0x39, 0x98, 0x10, 0x5a, 0xc4, + 0xdd, 0xa7, 0x4d, 0x6b, 0x4d, 0x99, 0x8d, 0xbd, 0xb7, 0x8c, 0xe1, 0xd9, 0xec, 0x14, 0x87, 0xe1, + 0x7a, 0xcd, 0x61, 0x33, 0x2d, 0xb3, 0xb3, 0x6a, 0xeb, 0x8e, 0x51, 0xa7, 0x96, 0xa1, 0xcc, 0xc5, + 0xd6, 0xeb, 0x14, 0x1a, 0x5c, 0xaf, 0x53, 0xe0, 0x18, 0x22, 0x2e, 0x06, 0xd7, 0x68, 0x93, 0x9a, + 0x0f, 0xa9, 0x72, 0x13, 0xd9, 0x4e, 0x66, 0xb1, 0x15, 0x64, 0xf3, 0x7d, 0x5a, 0x16, 0x07, 0x66, + 0xab, 0x2f, 0x6e, 0xd6, 0xdf, 0x59, 0x08, 0xc2, 0x0f, 0xd4, 0x1c, 0xda, 0xd1, 0x1d, 0xaa, 0xcc, + 0x47, 0x6d, 0xf5, 0x54, 0x22, 0x66, 0xab, 0xa7, 0x22, 0x92, 0x6c, 0xfd, 0xb1, 0x50, 0xed, 0xc5, + 0x36, 0x1c, 0x11, 0xe9, 0xa5, 0xd9, 0xec, 0x14, 0x45, 0x30, 0x01, 0x2d, 0xd8, 0xd6, 0x1a, 0x9e, + 0x54, 0xdc, 0x8a, 0xce, 0x4e, 0xd9, 0x94, 0x6c, 0x76, 0xca, 0xc6, 0x32, 0x55, 0x8f, 0x62, 0xf9, + 0x18, 0xbc, 0x1d, 0x55, 0xf5, 0x14, 0x12, 0xa6, 0xea, 0x29, 0xe0, 0x24, 0x43, 0x8d, 0xba, 0xd4, + 0x53, 0x16, 0x7a, 0x31, 0x44, 0x92, 0x24, 0x43, 0x04, 0x27, 0x19, 0xce, 0x51, 0xaf, 0xb9, 0xae, + 0x2c, 0xf6, 0x62, 0x88, 0x24, 0x49, 0x86, 0x08, 0x66, 0x9b, 0xcd, 0x28, 0x78, 0xba, 0xdb, 0x7a, + 0xe0, 0xf7, 0xd9, 0x52, 0x74, 0xb3, 0x99, 0x49, 0xc8, 0x36, 0x9b, 0x99, 0x48, 0xf2, 0xa3, 0xbb, + 0xf6, 0x83, 0x57, 0x96, 0xb1, 0xc2, 0xa9, 0xd0, 0x2e, 0xd8, 0x4d, 0xa9, 0xf9, 0x3e, 0x6d, 0xb7, + 0x7e, 0xf6, 0x1f, 0x0f, 0x9c, 0x46, 0x95, 0x1a, 0x56, 0x35, 0x1e, 0x9c, 0x55, 0x70, 0xf0, 0x7c, + 0x9f, 0x16, 0xb8, 0x95, 0xbe, 0x0a, 0xc3, 0xf8, 0x51, 0x55, 0xcb, 0xf4, 0x2a, 0xd3, 0xca, 0x3b, + 0xd1, 0x2d, 0x93, 0x84, 0x62, 0x5b, 0x26, 0xe9, 0x27, 0x9b, 0xc4, 0xf1, 0x27, 0x9f, 0x62, 0x2a, + 0xd3, 0x8a, 0x16, 0x9d, 0xc4, 0x23, 0x48, 0x36, 0x89, 0x47, 0x00, 0x41, 0xbd, 0x15, 0xc7, 0xee, + 0x54, 0xa6, 0x95, 0x7a, 0x4a, 0xbd, 0x1c, 0x15, 0xd4, 0xcb, 0x7f, 0x06, 0xf5, 0xd6, 0xd7, 0xbb, + 0x5e, 0x85, 0x7d, 0xe3, 0x4a, 0x4a, 0xbd, 0x3e, 0x32, 0xa8, 0xd7, 0x07, 0xb0, 0xa9, 0x10, 0x01, + 0x35, 0xc7, 0x66, 0x93, 0xf6, 0x6d, 0xb3, 0xd5, 0x52, 0xee, 0x44, 0xa7, 0xc2, 0x38, 0x9e, 0x4d, + 0x85, 0x71, 0x18, 0x33, 0x3d, 0x79, 0xab, 0xe8, 0x6a, 0x77, 0x4d, 0xb9, 0x1b, 0x35, 0x3d, 0x43, + 0x0c, 0x33, 0x3d, 0xc3, 0x5f, 0xb8, 0xbb, 0x60, 0xbf, 0x34, 0x7a, 0xdf, 0xa1, 0xee, 0xba, 0x72, + 0x2f, 0xb6, 0xbb, 0x90, 0x70, 0xb8, 0xbb, 0x90, 0x7e, 0x93, 0x35, 0x78, 0x22, 0xb2, 0xd0, 0xf8, + 0x97, 0x36, 0x75, 0xaa, 0x3b, 0xcd, 0x75, 0xe5, 0x33, 0xc8, 0xea, 0xd9, 0xd4, 0xa5, 0x2a, 0x4a, + 0x3a, 0xdf, 0xa7, 0xf5, 0xe2, 0x84, 0xdb, 0xf2, 0x77, 0x16, 0x78, 0x24, 0x58, 0xad, 0x36, 0xe3, + 0x6f, 0x42, 0xdf, 0x8d, 0x6d, 0xcb, 0x93, 0x24, 0xb8, 0x2d, 0x4f, 0x82, 0x49, 0x07, 0x9e, 0x8a, + 0x6d, 0xd5, 0x16, 0xf5, 0x16, 0xdb, 0x97, 0x50, 0xa3, 0xa6, 0x37, 0x1f, 0x50, 0x4f, 0xf9, 0x2c, + 0xf2, 0xbe, 0x9c, 0xb1, 0xe1, 0x8b, 0x51, 0xcf, 0xf7, 0x69, 0x3b, 0xf0, 0x23, 0x2a, 0x14, 0xeb, + 0x73, 0x2b, 0x35, 0xe5, 0xfb, 0xa2, 0xe7, 0x9b, 0x0c, 0x36, 0xdf, 0xa7, 0x21, 0x8e, 0x59, 0x69, + 0x77, 0x3a, 0x6b, 0x8e, 0x6e, 0x50, 0x6e, 0x68, 0xa1, 0xed, 0x26, 0x0c, 0xd0, 0xef, 0x8f, 0x5a, + 0x69, 0x59, 0x74, 0xcc, 0x4a, 0xcb, 0xc2, 0x31, 0x45, 0x8d, 0x24, 0x3d, 0x51, 0x3e, 0x17, 0x55, + 0xd4, 0x08, 0x92, 0x29, 0x6a, 0x34, 0x45, 0xca, 0x67, 0xe0, 0x5c, 0xb0, 0x9f, 0x17, 0xeb, 0x2f, + 0xef, 0x34, 0xe5, 0xf3, 0xc8, 0xe7, 0xa9, 0xc4, 0x65, 0x40, 0x84, 0x6a, 0xbe, 0x4f, 0xcb, 0x28, + 0xcf, 0x56, 0xdc, 0x44, 0x52, 0x30, 0x61, 0x5e, 0xfc, 0x40, 0x74, 0xc5, 0xcd, 0x20, 0x63, 0x2b, + 0x6e, 0x06, 0x2a, 0x95, 0xb9, 0x10, 0xaa, 0xbe, 0x03, 0xf3, 0x40, 0xa6, 0x59, 0x1c, 0x52, 0x99, + 0x0b, 0x4b, 0x6d, 0x75, 0x07, 0xe6, 0x81, 0xb5, 0x96, 0xc5, 0x81, 0x5c, 0x81, 0x52, 0xbd, 0xbe, + 0xa8, 0x75, 0x2d, 0xa5, 0x19, 0xf3, 0x96, 0x45, 0xe8, 0x7c, 0x9f, 0x26, 0xf0, 0xcc, 0x0c, 0x9a, + 0x6d, 0xe9, 0xae, 0x67, 0x36, 0x5d, 0x1c, 0x31, 0xfe, 0x08, 0x31, 0xa2, 0x66, 0x50, 0x1a, 0x0d, + 0x33, 0x83, 0xd2, 0xe0, 0xcc, 0x5e, 0x9c, 0xd1, 0x5d, 0x57, 0xb7, 0x0c, 0x47, 0x9f, 0xc6, 0x65, + 0x82, 0xc6, 0x5e, 0x63, 0x45, 0xb0, 0xcc, 0x5e, 0x8c, 0x42, 0xf0, 0xf0, 0xdd, 0x87, 0xf8, 0x66, + 0xce, 0xfd, 0xd8, 0xe1, 0x7b, 0x0c, 0x8f, 0x87, 0xef, 0x31, 0x18, 0xda, 0x9d, 0x3e, 0x4c, 0xa3, + 0x6b, 0x26, 0x13, 0x91, 0xb2, 0x16, 0xb3, 0x3b, 0xe3, 0x04, 0x68, 0x77, 0xc6, 0x81, 0x91, 0x26, + 0xf9, 0xcb, 0xed, 0x7a, 0x46, 0x93, 0xc2, 0x55, 0x36, 0x51, 0x86, 0xad, 0xdf, 0xe1, 0xe0, 0xa8, + 0x6c, 0x5a, 0x7a, 0xdb, 0xae, 0x4c, 0xfb, 0x52, 0x37, 0xa3, 0xeb, 0x77, 0x26, 0x21, 0x5b, 0xbf, + 0x33, 0x91, 0x6c, 0x76, 0xf5, 0x37, 0x5a, 0xeb, 0xba, 0x43, 0x8d, 0x8a, 0xe9, 0xe0, 0xc9, 0xe2, + 0x26, 0xdf, 0x1a, 0xbe, 0x17, 0x9d, 0x5d, 0x7b, 0x90, 0xb2, 0xd9, 0xb5, 0x07, 0x9a, 0x19, 0x79, + 0xe9, 0x68, 0x8d, 0xea, 0x86, 0xf2, 0x20, 0x6a, 0xe4, 0x65, 0x53, 0x32, 0x23, 0x2f, 0x1b, 0x9b, + 0xfd, 0x39, 0xf7, 0x1c, 0xd3, 0xa3, 0x4a, 0x6b, 0x37, 0x9f, 0x83, 0xa4, 0xd9, 0x9f, 0x83, 0x68, + 0xb6, 0x21, 0x8c, 0x77, 0x48, 0x3b, 0xba, 0x21, 0x4c, 0x76, 0x43, 0xbc, 0x04, 0xb3, 0x58, 0xc4, + 0xa3, 0x3c, 0xc5, 0x8a, 0x5a, 0x2c, 0x02, 0xcc, 0x2c, 0x96, 0xf0, 0xd9, 0x5e, 0xe4, 0x29, 0x96, + 0x62, 0x47, 0xd7, 0x50, 0x19, 0xc7, 0xd6, 0xd0, 0xc8, 0xb3, 0xad, 0x57, 0x23, 0xef, 0x0c, 0x94, + 0x4e, 0xd4, 0xea, 0x90, 0x50, 0xcc, 0xea, 0x90, 0x5f, 0x24, 0xcc, 0xc0, 0x38, 0xde, 0x82, 0x6b, + 0xdd, 0xe0, 0x1e, 0xe7, 0x07, 0xa3, 0x9f, 0x19, 0x43, 0xb3, 0xcf, 0x8c, 0x81, 0x22, 0x4c, 0xc4, + 0xb4, 0xe5, 0x64, 0x30, 0x09, 0xcf, 0x07, 0x63, 0x20, 0xb2, 0x00, 0xa4, 0x5e, 0x5e, 0x5c, 0xa8, + 0x1a, 0x35, 0xf9, 0x8a, 0xcc, 0x8d, 0x9e, 0xc0, 0x26, 0x29, 0xe6, 0xfb, 0xb4, 0x94, 0x72, 0xe4, + 0x3d, 0xb8, 0x24, 0xa0, 0xe2, 0xc5, 0x75, 0xcd, 0xb1, 0x1f, 0x9a, 0x46, 0xb0, 0x20, 0x78, 0x51, + 0x3f, 0xb6, 0x5e, 0xb4, 0xf3, 0x7d, 0x5a, 0x4f, 0x5e, 0xd9, 0x75, 0x89, 0xf5, 0xa1, 0xbb, 0x9b, + 0xba, 0x82, 0x45, 0xa2, 0x27, 0xaf, 0xec, 0xba, 0x84, 0xdc, 0x1f, 0xee, 0xa6, 0xae, 0xa0, 0x13, + 0x7a, 0xf2, 0x22, 0x2e, 0x4c, 0xf6, 0xc2, 0x97, 0x5b, 0x2d, 0x65, 0x03, 0xab, 0xfb, 0xd8, 0x6e, + 0xaa, 0x2b, 0xa3, 0xc1, 0xb9, 0x13, 0x47, 0x36, 0x4b, 0x2f, 0x77, 0xa8, 0x55, 0x8f, 0x2c, 0x40, + 0x8f, 0xa2, 0xb3, 0x74, 0x82, 0x80, 0xcd, 0xd2, 0x09, 0x20, 0x1b, 0x50, 0xf2, 0x73, 0x15, 0x65, + 0x33, 0x3a, 0xa0, 0x64, 0x1c, 0x1b, 0x50, 0x91, 0xa7, 0x2d, 0xcb, 0x70, 0x7a, 0xf9, 0x81, 0xa7, + 0xfb, 0x16, 0xa4, 0x2b, 0xba, 0xf2, 0xfd, 0xd8, 0x25, 0x53, 0x92, 0x04, 0x2f, 0x99, 0x92, 0x60, + 0x36, 0x46, 0x18, 0xb8, 0xbe, 0x69, 0x35, 0xe7, 0x74, 0xb3, 0xd5, 0x75, 0xa8, 0xf2, 0x67, 0xa2, + 0x63, 0x24, 0x86, 0x66, 0x63, 0x24, 0x06, 0x62, 0x0b, 0x34, 0x03, 0x95, 0x5d, 0xd7, 0x5c, 0xb3, + 0xc4, 0xbe, 0xb2, 0xdb, 0xf2, 0x94, 0x7f, 0x23, 0xba, 0x40, 0xa7, 0xd1, 0xb0, 0x05, 0x3a, 0x0d, + 0x8e, 0xa7, 0x4e, 0xac, 0x17, 0xd8, 0xe2, 0x21, 0xdf, 0x55, 0xfe, 0x9b, 0xb1, 0x53, 0xa7, 0x14, + 0x1a, 0x3c, 0x75, 0x4a, 0x81, 0xb3, 0xf5, 0x91, 0xdb, 0x64, 0x0b, 0x66, 0x70, 0x57, 0xfd, 0x6f, + 0x45, 0xd7, 0xc7, 0x38, 0x9e, 0xad, 0x8f, 0x71, 0x58, 0x94, 0x8f, 0xe8, 0x82, 0x7f, 0x3b, 0x8b, + 0x4f, 0x20, 0xff, 0x44, 0x19, 0x72, 0x53, 0xe6, 0x23, 0x46, 0xca, 0x0f, 0xe5, 0xb2, 0x18, 0x05, + 0xc3, 0x23, 0x51, 0x28, 0xca, 0x48, 0xa3, 0x0f, 0x4d, 0xba, 0xa1, 0x7c, 0x21, 0x93, 0x11, 0x27, + 0x88, 0x32, 0xe2, 0x30, 0xf2, 0x2e, 0x9c, 0x0b, 0x61, 0x8b, 0xb4, 0xbd, 0x1a, 0xcc, 0x4c, 0x7f, + 0x36, 0x17, 0x35, 0x83, 0xd3, 0xc9, 0x98, 0x19, 0x9c, 0x8e, 0x49, 0x63, 0x2d, 0x44, 0xf7, 0xef, + 0xec, 0xc0, 0x3a, 0x90, 0x60, 0x06, 0x83, 0x34, 0xd6, 0x42, 0x9a, 0x3f, 0xbc, 0x03, 0xeb, 0x40, + 0xa6, 0x19, 0x0c, 0xc8, 0x8f, 0xe5, 0xe0, 0x72, 0x3a, 0xaa, 0xdc, 0x6a, 0xcd, 0xd9, 0x4e, 0x88, + 0x53, 0xfe, 0x5c, 0x2e, 0x7a, 0xd0, 0xb0, 0xbb, 0x62, 0xf3, 0x7d, 0xda, 0x2e, 0x2b, 0x20, 0x9f, + 0x82, 0xd1, 0x72, 0xd7, 0x30, 0x3d, 0xbc, 0x78, 0x63, 0x86, 0xf3, 0x8f, 0xe4, 0x62, 0x5b, 0x1c, + 0x19, 0x8b, 0x5b, 0x1c, 0x19, 0x40, 0x6e, 0xc1, 0xa9, 0x3a, 0x6d, 0x76, 0x1d, 0xd3, 0xdb, 0xd4, + 0x68, 0xc7, 0x76, 0x3c, 0xc6, 0xe3, 0xcf, 0xe7, 0xa2, 0x93, 0x58, 0x82, 0x82, 0x4d, 0x62, 0x09, + 0x20, 0xb9, 0x9b, 0xb8, 0x95, 0x17, 0x9d, 0xf9, 0xa3, 0xb9, 0x9e, 0xd7, 0xf2, 0x41, 0x5f, 0xa6, + 0x17, 0x27, 0xb5, 0xd8, 0x2d, 0xba, 0xe0, 0xfa, 0x63, 0xb9, 0x1e, 0xd7, 0xe8, 0xd2, 0x0c, 0x97, + 0x04, 0x33, 0x8e, 0x29, 0x69, 0xe4, 0x95, 0xbf, 0x90, 0xeb, 0x71, 0xed, 0x1d, 0x72, 0x4c, 0xcb, + 0x40, 0xff, 0x32, 0xf7, 0x14, 0x11, 0x8c, 0x7e, 0x3c, 0x97, 0x74, 0x15, 0x09, 0xca, 0x4b, 0x84, + 0xac, 0xd8, 0x1d, 0x37, 0x50, 0xfa, 0x2f, 0xe6, 0x92, 0xbe, 0x79, 0x61, 0xb1, 0xf0, 0x17, 0xa1, + 0x70, 0x71, 0xf6, 0x91, 0x47, 0x1d, 0x4b, 0x6f, 0x61, 0x77, 0xd6, 0x3d, 0xdb, 0xd1, 0xd7, 0xe8, + 0xac, 0xa5, 0xaf, 0xb6, 0xa8, 0xf2, 0x13, 0xb9, 0xa8, 0x05, 0x9b, 0x4d, 0xca, 0x2c, 0xd8, 0x6c, + 0x2c, 0x59, 0x87, 0x27, 0xd2, 0xb0, 0x15, 0xd3, 0xc5, 0x7a, 0xbe, 0x94, 0x8b, 0x9a, 0xb0, 0x3d, + 0x68, 0x99, 0x09, 0xdb, 0x03, 0x4d, 0xae, 0xc3, 0xd0, 0xb4, 0xed, 0x4f, 0xbf, 0x7f, 0x31, 0xe6, + 0x0c, 0x19, 0x60, 0xe6, 0xfb, 0xb4, 0x90, 0x4c, 0x94, 0x11, 0x83, 0xfa, 0xcb, 0xc9, 0x32, 0xe1, + 0xe5, 0x53, 0xf0, 0x43, 0x94, 0x11, 0xe2, 0xfe, 0x77, 0x93, 0x65, 0xc2, 0x3b, 0xae, 0xe0, 0x07, + 0x9b, 0x49, 0x78, 0x8d, 0x8b, 0x73, 0x65, 0x66, 0xb7, 0xcd, 0xac, 0xeb, 0xad, 0x16, 0xb5, 0xd6, + 0xa8, 0xf2, 0x95, 0xd8, 0x4c, 0x92, 0x4e, 0xc6, 0x66, 0x92, 0x74, 0x0c, 0xf9, 0x7e, 0x38, 0x7f, + 0x57, 0x6f, 0x99, 0x46, 0x88, 0xf3, 0x93, 0x8a, 0x2b, 0x3f, 0x99, 0x8b, 0xee, 0xa6, 0x33, 0xe8, + 0xd8, 0x6e, 0x3a, 0x03, 0x45, 0x16, 0x81, 0xe0, 0x32, 0x1a, 0xcc, 0x16, 0x6c, 0x7d, 0x56, 0xfe, + 0xbd, 0x5c, 0xd4, 0x4e, 0x4d, 0x92, 0x30, 0x3b, 0x35, 0x09, 0x25, 0x8d, 0xec, 0xd4, 0x20, 0xca, + 0x4f, 0xe5, 0xa2, 0xa7, 0x35, 0x59, 0x84, 0xf3, 0x7d, 0x5a, 0x76, 0x7e, 0x91, 0x9b, 0x30, 0x51, + 0xaf, 0x55, 0xe7, 0xe6, 0x66, 0xeb, 0x77, 0xab, 0x15, 0x7c, 0xe8, 0x60, 0x28, 0x3f, 0x1d, 0x5b, + 0xb1, 0xe2, 0x04, 0x6c, 0xc5, 0x8a, 0xc3, 0xc8, 0x1b, 0x30, 0xc2, 0xda, 0xcf, 0x06, 0x0c, 0x7e, + 0xf2, 0x57, 0x73, 0x51, 0x73, 0x4a, 0x46, 0x32, 0x73, 0x4a, 0xfe, 0x4d, 0xea, 0x70, 0x86, 0x49, + 0xb1, 0xe6, 0xd0, 0xfb, 0xd4, 0xa1, 0x56, 0xd3, 0x1f, 0xd3, 0x3f, 0x93, 0x8b, 0x5a, 0x19, 0x69, + 0x44, 0xcc, 0xca, 0x48, 0x83, 0x93, 0x07, 0x70, 0x29, 0x7e, 0x12, 0x24, 0x3f, 0x3b, 0x55, 0xfe, + 0x52, 0x2e, 0x66, 0x0c, 0xf7, 0x20, 0x46, 0x63, 0xb8, 0x07, 0x9e, 0x58, 0xf0, 0xa4, 0x38, 0x56, + 0x11, 0x0e, 0x97, 0xf1, 0xda, 0x7e, 0x96, 0xd7, 0xf6, 0xd1, 0xd0, 0x21, 0xb0, 0x07, 0xf5, 0x7c, + 0x9f, 0xd6, 0x9b, 0x1d, 0xd3, 0xb3, 0x64, 0x02, 0x0c, 0xe5, 0x2f, 0xe7, 0xd2, 0x3d, 0x52, 0x22, + 0x6e, 0xca, 0x69, 0x99, 0x33, 0xde, 0xcd, 0x4a, 0xdf, 0xa0, 0xfc, 0x95, 0xd8, 0x78, 0x4b, 0x27, + 0x63, 0xe3, 0x2d, 0x23, 0xff, 0xc3, 0x2d, 0x38, 0xc5, 0x95, 0xba, 0xa6, 0xe3, 0x30, 0xb4, 0xd6, + 0xa8, 0xa1, 0xfc, 0xfb, 0xb1, 0xd5, 0x2e, 0x41, 0x81, 0xae, 0x3d, 0x71, 0x20, 0x9b, 0xba, 0xeb, + 0x1d, 0xdd, 0xb2, 0xf0, 0x98, 0x55, 0xf9, 0x0f, 0x62, 0x53, 0x77, 0x88, 0x42, 0xc7, 0xdd, 0xe0, + 0x17, 0xd3, 0x84, 0x5e, 0xa9, 0x8f, 0x94, 0xff, 0x30, 0xa6, 0x09, 0xbd, 0x88, 0x99, 0x26, 0xf4, + 0xcc, 0xa3, 0x74, 0x37, 0xe3, 0x09, 0xb8, 0xf2, 0xb5, 0xd8, 0x8a, 0x9c, 0x4a, 0xc5, 0x56, 0xe4, + 0xf4, 0x17, 0xe4, 0x77, 0x33, 0x9e, 0x4f, 0x2b, 0x3f, 0xd7, 0x9b, 0x6f, 0xb8, 0xd2, 0xa7, 0xbf, + 0xbe, 0xbe, 0x9b, 0xf1, 0xf4, 0x58, 0xf9, 0xab, 0xbd, 0xf9, 0x86, 0x8e, 0x7d, 0xe9, 0x2f, 0x97, + 0x1b, 0xd9, 0xcf, 0x76, 0x95, 0xbf, 0x16, 0x9f, 0xba, 0x32, 0x08, 0x71, 0xea, 0xca, 0x7a, 0xfb, + 0xbb, 0x0a, 0x17, 0xb8, 0x86, 0xdc, 0x74, 0xf4, 0xce, 0x7a, 0x9d, 0x7a, 0x9e, 0x69, 0xad, 0xf9, + 0x3b, 0xb1, 0xff, 0x28, 0x17, 0x3b, 0x1e, 0xcb, 0xa2, 0xc4, 0xe3, 0xb1, 0x2c, 0x24, 0x53, 0xde, + 0xc4, 0x03, 0x5d, 0xe5, 0xaf, 0xc7, 0x94, 0x37, 0x41, 0xc1, 0x94, 0x37, 0xf9, 0xae, 0xf7, 0x56, + 0xca, 0x3b, 0x54, 0xe5, 0x3f, 0xce, 0xe6, 0x15, 0xb4, 0x2f, 0xe5, 0xf9, 0xea, 0xad, 0x94, 0xe7, + 0x96, 0xca, 0x7f, 0x92, 0xcd, 0x2b, 0xf4, 0x41, 0x4a, 0xbe, 0xd2, 0x7c, 0x17, 0xce, 0xf1, 0xd9, + 0x7c, 0x8e, 0x1a, 0x34, 0xf2, 0xa1, 0x3f, 0x1f, 0x1b, 0xfb, 0xe9, 0x64, 0x78, 0xe4, 0x9e, 0x8a, + 0x49, 0x63, 0x2d, 0xda, 0xfa, 0x37, 0x76, 0x60, 0x1d, 0x6e, 0x08, 0xd2, 0x31, 0x6c, 0xbd, 0x91, + 0x1f, 0xbf, 0x29, 0xbf, 0x10, 0x5b, 0x6f, 0x64, 0x24, 0xba, 0x73, 0xc8, 0x2f, 0xe5, 0xde, 0x88, + 0x3e, 0xf4, 0x52, 0xfe, 0x66, 0x6a, 0xe1, 0xa0, 0x03, 0xa2, 0xaf, 0xc2, 0xde, 0x88, 0x3e, 0x6a, + 0x52, 0x7e, 0x31, 0xb5, 0x70, 0xf0, 0x01, 0xd1, 0x17, 0x50, 0x6c, 0x8b, 0xd4, 0xf5, 0x6c, 0xce, + 0x2a, 0x32, 0x3d, 0xfc, 0xad, 0xf8, 0x16, 0x29, 0x95, 0x0c, 0xb7, 0x48, 0xa9, 0x98, 0x34, 0xd6, + 0xe2, 0xf3, 0x7e, 0x69, 0x07, 0xd6, 0xd2, 0xc6, 0x2e, 0x15, 0x93, 0xc6, 0x5a, 0x7c, 0xfc, 0xd7, + 0x77, 0x60, 0x2d, 0x6d, 0xec, 0x52, 0x31, 0xcc, 0x1c, 0x0b, 0x31, 0x77, 0xa9, 0xe3, 0x86, 0xea, + 0xf7, 0x9f, 0xc6, 0xcc, 0xb1, 0x0c, 0x3a, 0x66, 0x8e, 0x65, 0xa0, 0x52, 0xb9, 0x0b, 0xa1, 0xfc, + 0xf2, 0x4e, 0xdc, 0xc3, 0x7b, 0x99, 0x0c, 0x54, 0x2a, 0x77, 0x21, 0x97, 0xbf, 0xbd, 0x13, 0xf7, + 0xf0, 0x62, 0x26, 0x03, 0xc5, 0x8c, 0xa2, 0xba, 0xa7, 0x7b, 0x66, 0x73, 0xde, 0x76, 0x3d, 0x69, + 0x91, 0xff, 0xcf, 0x62, 0x46, 0x51, 0x1a, 0x11, 0x33, 0x8a, 0xd2, 0xe0, 0x49, 0xa6, 0x42, 0x1a, + 0xbf, 0xd2, 0x93, 0x69, 0x68, 0x69, 0xa5, 0xc1, 0x93, 0x4c, 0x85, 0x10, 0xfe, 0xf3, 0x9e, 0x4c, + 0x43, 0x4f, 0xf9, 0x34, 0x38, 0xb3, 0x4c, 0x67, 0x1c, 0x7b, 0xc3, 0xba, 0x45, 0x37, 0x68, 0x4b, + 0x7c, 0xfa, 0xaf, 0xc6, 0x2c, 0xd3, 0x38, 0x01, 0xde, 0xa2, 0xc4, 0x60, 0x51, 0x46, 0xe2, 0x73, + 0x7f, 0x2d, 0x93, 0x51, 0x78, 0x4c, 0x14, 0x87, 0x45, 0x19, 0x89, 0x4f, 0xfc, 0xf5, 0x4c, 0x46, + 0xe1, 0x31, 0x51, 0x1c, 0x46, 0xca, 0x30, 0x86, 0x6f, 0x25, 0x74, 0xd7, 0xf7, 0xfc, 0xfc, 0xad, + 0x5c, 0xf4, 0xd6, 0x2b, 0x8a, 0x9e, 0xef, 0xd3, 0x62, 0x05, 0x64, 0x16, 0xe2, 0x93, 0xbe, 0x99, + 0xc1, 0x22, 0xf4, 0x77, 0x8c, 0x42, 0x64, 0x16, 0xe2, 0x63, 0xfe, 0x8b, 0x0c, 0x16, 0xa1, 0xc3, + 0x63, 0x14, 0x42, 0x3e, 0x09, 0xc3, 0xf5, 0xb9, 0x95, 0x9a, 0x9f, 0x9e, 0xef, 0xef, 0xe4, 0x62, + 0xaf, 0x8a, 0x42, 0x1c, 0xbe, 0x2a, 0x0a, 0x7f, 0x92, 0x4f, 0xc1, 0xe8, 0x8c, 0x6d, 0x79, 0x7a, + 0xd3, 0xdf, 0x80, 0xfe, 0x76, 0xec, 0x0c, 0x25, 0x82, 0x9d, 0xef, 0xd3, 0xa2, 0xe4, 0x52, 0x79, + 0xd1, 0xf6, 0xdf, 0x49, 0x2f, 0x1f, 0x34, 0x3d, 0x4a, 0xce, 0x66, 0xb4, 0x7b, 0xb6, 0xf3, 0xa0, + 0x65, 0xeb, 0x86, 0x1f, 0x11, 0x52, 0x34, 0xe4, 0xef, 0xc6, 0x66, 0xb4, 0x74, 0x32, 0x36, 0xa3, + 0xa5, 0x63, 0xd2, 0x58, 0x8b, 0x2e, 0xfa, 0xd6, 0x0e, 0xac, 0xc3, 0x79, 0x38, 0x1d, 0x93, 0xc6, + 0x5a, 0x7c, 0xfe, 0xdf, 0xdb, 0x81, 0x75, 0x38, 0x0f, 0xa7, 0x63, 0x08, 0x85, 0x8b, 0xc1, 0x9b, + 0xc7, 0x70, 0x13, 0x5a, 0xb5, 0x1e, 0xb2, 0x8d, 0xae, 0xf2, 0xf7, 0x63, 0xc7, 0x1b, 0xd9, 0xa4, + 0xf3, 0x7d, 0x5a, 0x0f, 0x46, 0xd3, 0x03, 0xd0, 0x8f, 0x47, 0xe8, 0xb7, 0x4a, 0x83, 0xdf, 0xc8, + 0x4d, 0xfc, 0x46, 0xee, 0x56, 0x69, 0xf0, 0x37, 0x72, 0x13, 0xbf, 0xc9, 0xfe, 0xff, 0xcd, 0xdc, + 0xc4, 0x6f, 0xe5, 0xb4, 0x0b, 0xe1, 0x74, 0x58, 0x5e, 0xa3, 0x96, 0x57, 0x6b, 0xe9, 0x62, 0x32, + 0x4f, 0x45, 0xf1, 0x9f, 0xa9, 0x28, 0x91, 0x02, 0xed, 0x6b, 0x39, 0x18, 0xa9, 0x7b, 0x0e, 0xd5, + 0xdb, 0x22, 0xa2, 0xe0, 0x45, 0x18, 0xe4, 0x4e, 0xef, 0xfe, 0x0b, 0x7d, 0x2d, 0xf8, 0x4d, 0x2e, + 0xc3, 0xd8, 0x82, 0xee, 0x7a, 0xd8, 0xc4, 0xaa, 0x65, 0xd0, 0x47, 0xf8, 0xe0, 0xb3, 0xa0, 0xc5, + 0xa0, 0x64, 0x81, 0xd3, 0xf1, 0x72, 0x18, 0x44, 0xb6, 0xb0, 0x63, 0x20, 0xbd, 0xc1, 0x6f, 0x6f, + 0x4d, 0xf6, 0x61, 0xdc, 0xbc, 0x58, 0x59, 0xf5, 0xf7, 0x72, 0x90, 0x70, 0xc7, 0xdf, 0x7f, 0xe4, + 0x8c, 0x65, 0x18, 0x8f, 0x05, 0x2e, 0x16, 0xaf, 0x56, 0x77, 0x19, 0xd7, 0x38, 0x5e, 0x9a, 0x7c, + 0x2c, 0x78, 0x2d, 0x79, 0x47, 0x5b, 0x10, 0x41, 0x12, 0x31, 0xbd, 0x47, 0xd7, 0x69, 0x69, 0x12, + 0x4a, 0x04, 0xc1, 0xfa, 0xee, 0x44, 0x18, 0x95, 0x95, 0x5c, 0x16, 0x61, 0x3c, 0x72, 0x61, 0x68, + 0xc5, 0xae, 0x4b, 0x1d, 0x39, 0xb4, 0x22, 0x86, 0xed, 0xf8, 0x14, 0x8c, 0x54, 0xdb, 0x1d, 0xea, + 0xb8, 0xb6, 0xa5, 0x7b, 0xb6, 0x23, 0xa2, 0x20, 0x60, 0xd8, 0x3d, 0x53, 0x82, 0xcb, 0xa1, 0xe0, + 0x64, 0x7a, 0x72, 0xd5, 0xcf, 0x50, 0x58, 0xc0, 0x78, 0xb8, 0xf8, 0x94, 0x39, 0x9e, 0xa0, 0x9e, + 0x53, 0x30, 0xd2, 0x3b, 0xae, 0x8e, 0xef, 0x6a, 0x03, 0xd2, 0x2e, 0x03, 0xc8, 0xa4, 0x48, 0x41, + 0x9e, 0x87, 0x12, 0xea, 0xb1, 0x8b, 0x99, 0x47, 0x45, 0xc0, 0xc7, 0x16, 0x42, 0xe4, 0xf0, 0x7a, + 0x9c, 0x86, 0xdc, 0x86, 0x89, 0xd0, 0xc9, 0xe2, 0xa6, 0x63, 0x77, 0x3b, 0x7e, 0xae, 0x21, 0x4c, + 0xec, 0xff, 0x20, 0xc0, 0x35, 0xd6, 0x10, 0x29, 0xb1, 0x48, 0x14, 0x24, 0xf3, 0x30, 0x1e, 0xc2, + 0x98, 0x88, 0xfc, 0x1c, 0x67, 0x98, 0x5f, 0x56, 0xe2, 0xc5, 0xc4, 0x19, 0xc9, 0x2f, 0x1b, 0x2b, + 0x46, 0xaa, 0x30, 0xe0, 0x47, 0x7b, 0x1c, 0xdc, 0x51, 0x49, 0x4f, 0x8b, 0x68, 0x8f, 0x03, 0x72, + 0x9c, 0x47, 0xbf, 0x3c, 0x99, 0x83, 0x31, 0xcd, 0xee, 0x7a, 0x74, 0xc5, 0x16, 0xa7, 0x13, 0x22, + 0xaa, 0x28, 0xb6, 0xc9, 0x61, 0x98, 0x86, 0x67, 0x37, 0x9a, 0x1c, 0x27, 0xe7, 0xe7, 0x8f, 0x96, + 0x22, 0x4b, 0x70, 0x2a, 0xe1, 0x8e, 0x82, 0xcf, 0x70, 0x87, 0x78, 0xdc, 0x3e, 0xe9, 0xf3, 0x92, + 0xcc, 0x92, 0x45, 0xc9, 0x8f, 0xe4, 0xa0, 0xb4, 0xe2, 0xe8, 0xa6, 0xe7, 0x8a, 0x27, 0xb9, 0x67, + 0xa7, 0x36, 0x1c, 0xbd, 0xc3, 0xf4, 0x63, 0x0a, 0x03, 0x1e, 0xdf, 0xd5, 0x5b, 0x5d, 0xea, 0x4e, + 0xdf, 0x63, 0x5f, 0xf7, 0xdf, 0x6f, 0x4d, 0xbe, 0xb1, 0x86, 0x87, 0xde, 0x53, 0x4d, 0xbb, 0x7d, + 0x6d, 0xcd, 0xd1, 0x1f, 0x9a, 0x1e, 0x6e, 0x2d, 0xf4, 0xd6, 0x35, 0x8f, 0xb6, 0xf0, 0x6c, 0xfd, + 0x9a, 0xde, 0x31, 0xaf, 0x61, 0x60, 0xfd, 0x6b, 0x01, 0x27, 0x5e, 0x03, 0x53, 0x01, 0x0f, 0xff, + 0x92, 0x55, 0x80, 0xe3, 0xc8, 0x12, 0x80, 0xf8, 0xd4, 0x72, 0xa7, 0x23, 0xde, 0xf7, 0x4a, 0x27, + 0xd2, 0x3e, 0x86, 0x2b, 0x76, 0x20, 0x30, 0xbd, 0x23, 0x05, 0x93, 0xd6, 0x24, 0x0e, 0x4c, 0x0b, + 0x56, 0x44, 0x8b, 0x7c, 0x31, 0x8d, 0x86, 0x12, 0xf7, 0x1b, 0x9b, 0x22, 0xa4, 0x78, 0x31, 0xb2, + 0x0a, 0xe3, 0x82, 0x6f, 0x90, 0x7a, 0x66, 0x2c, 0x3a, 0x2b, 0xc4, 0xd0, 0x5c, 0x69, 0x83, 0x36, + 0x1a, 0x02, 0x2c, 0xd7, 0x11, 0x2b, 0x41, 0xa6, 0xc3, 0x54, 0xd9, 0x4b, 0x7a, 0x9b, 0xba, 0xca, + 0x38, 0x6a, 0xec, 0xa5, 0xed, 0xad, 0x49, 0xc5, 0x2f, 0x8f, 0x81, 0x4f, 0x65, 0xd1, 0x45, 0x8b, + 0xc8, 0x3c, 0xb8, 0xd6, 0x4f, 0xa4, 0xf0, 0x88, 0xeb, 0x7c, 0xb4, 0x08, 0x99, 0x81, 0xd1, 0xe0, + 0x79, 0xd1, 0x9d, 0x3b, 0xd5, 0x0a, 0x3e, 0x20, 0x16, 0xb1, 0x6f, 0x63, 0xc9, 0x61, 0x64, 0x26, + 0x91, 0x32, 0x52, 0x4c, 0x16, 0xfe, 0xa2, 0x38, 0x16, 0x93, 0xa5, 0x93, 0x12, 0x93, 0xa5, 0x46, + 0xde, 0x82, 0xe1, 0xf2, 0xbd, 0xba, 0x88, 0x35, 0xe3, 0x2a, 0xa7, 0xc3, 0x4c, 0x63, 0xfa, 0x86, + 0xdb, 0xf0, 0xe3, 0xd2, 0xc8, 0x4d, 0x97, 0xe9, 0xc9, 0x2c, 0x8c, 0x45, 0x3c, 0x14, 0x5d, 0xe5, + 0x0c, 0x72, 0xc0, 0x96, 0xeb, 0x88, 0x69, 0x38, 0x02, 0x25, 0x0f, 0xaf, 0x68, 0x21, 0xa6, 0x35, + 0x15, 0xd3, 0xc5, 0xac, 0x4d, 0x1a, 0xc5, 0xb0, 0x36, 0xf8, 0x1c, 0x79, 0x90, 0x6b, 0x8d, 0x21, + 0x50, 0x0d, 0x87, 0xe3, 0xe4, 0x1e, 0x8d, 0x15, 0x23, 0xef, 0x01, 0xc1, 0x3c, 0x4f, 0xd4, 0xf0, + 0x2f, 0xac, 0xab, 0x15, 0x57, 0x39, 0x87, 0x81, 0xdf, 0x49, 0x3c, 0x8c, 0x46, 0xb5, 0x32, 0x7d, + 0x59, 0x4c, 0x1f, 0x4f, 0xe9, 0xbc, 0x54, 0xc3, 0x0f, 0xa1, 0xd1, 0x30, 0x23, 0x49, 0xb0, 0x53, + 0xb8, 0x92, 0x0d, 0x38, 0x5f, 0x73, 0xe8, 0x43, 0xd3, 0xee, 0xba, 0xfe, 0xf2, 0xe1, 0xcf, 0x5b, + 0xe7, 0x77, 0x9c, 0xb7, 0x9e, 0x11, 0x15, 0x9f, 0xed, 0x38, 0xf4, 0x61, 0xc3, 0x0f, 0xf7, 0x1d, + 0x89, 0x56, 0x9b, 0xc5, 0x1d, 0x53, 0x79, 0xbf, 0xdf, 0x75, 0xa8, 0x80, 0x9b, 0xd4, 0x55, 0x94, + 0x70, 0xaa, 0xe5, 0x11, 0x8a, 0xcc, 0x00, 0x17, 0x49, 0xe5, 0x1d, 0x2d, 0x46, 0x34, 0x20, 0x37, + 0x67, 0x7c, 0xe7, 0x85, 0x72, 0x93, 0x27, 0x3c, 0x56, 0x2e, 0x20, 0x33, 0x95, 0x89, 0x65, 0xad, + 0x19, 0x84, 0xfe, 0x6f, 0xe8, 0x02, 0x2f, 0x8b, 0x25, 0x59, 0x9a, 0x2c, 0xc0, 0x44, 0xcd, 0xc1, + 0xa3, 0xd4, 0xdb, 0x74, 0xb3, 0x66, 0xb7, 0xcc, 0xe6, 0x26, 0xbe, 0x8a, 0x16, 0x53, 0x65, 0x87, + 0xe3, 0x1a, 0x0f, 0xe8, 0x66, 0xa3, 0x83, 0x58, 0x79, 0x59, 0x89, 0x97, 0x94, 0x43, 0x71, 0x3f, + 0xb1, 0xbb, 0x50, 0xdc, 0x14, 0x26, 0x84, 0xeb, 0xc3, 0x23, 0x8f, 0x5a, 0x6c, 0xa9, 0x77, 0xc5, + 0x0b, 0x68, 0x25, 0xe6, 0x2a, 0x11, 0xe0, 0xf9, 0xd4, 0x21, 0x46, 0x19, 0x0d, 0xc0, 0x72, 0xc3, + 0xe2, 0x45, 0x92, 0xf1, 0xaa, 0x9f, 0xdc, 0x47, 0xbc, 0xea, 0xff, 0xbd, 0x20, 0xcf, 0xbf, 0xe4, + 0x12, 0x14, 0xa5, 0x74, 0x52, 0x18, 0x8c, 0x17, 0x43, 0xef, 0x17, 0x45, 0x8c, 0xf1, 0x21, 0x61, + 0xbb, 0x04, 0x51, 0x97, 0x30, 0x7f, 0x68, 0x18, 0xa0, 0x55, 0x0b, 0x09, 0x30, 0x77, 0x63, 0x77, + 0xb5, 0x65, 0x36, 0x31, 0x21, 0x43, 0x41, 0x0a, 0xb3, 0x82, 0x50, 0x9e, 0x8f, 0x41, 0x22, 0x21, + 0xd7, 0x61, 0xd8, 0x3f, 0xc2, 0x0f, 0x83, 0x51, 0x63, 0x9c, 0x7e, 0x31, 0x5b, 0x8b, 0x34, 0x00, + 0x12, 0x11, 0x79, 0x1d, 0x20, 0x9c, 0x0e, 0x84, 0xa5, 0x85, 0x4b, 0x85, 0x3c, 0x7b, 0xc8, 0x4b, + 0x45, 0x48, 0xcd, 0x26, 0x4e, 0x59, 0x1d, 0xfd, 0x6c, 0xb5, 0x38, 0x71, 0x46, 0x74, 0x58, 0x56, + 0x90, 0x68, 0x11, 0xb2, 0x0c, 0xa7, 0x12, 0x1a, 0x28, 0x42, 0x57, 0x3f, 0xb3, 0xbd, 0x35, 0xf9, + 0x64, 0x8a, 0xfa, 0xca, 0x0b, 0x73, 0xa2, 0x2c, 0x79, 0x16, 0x0a, 0x77, 0xb4, 0xaa, 0x08, 0x9f, + 0xcb, 0x23, 0x2f, 0x47, 0x62, 0x6b, 0x31, 0x2c, 0x79, 0x0d, 0x80, 0xa7, 0xa7, 0xa9, 0xd9, 0x8e, + 0x87, 0x16, 0xc5, 0xe8, 0xf4, 0x05, 0x36, 0x96, 0x79, 0xfa, 0x9a, 0x06, 0x5b, 0xc6, 0xe4, 0x8f, + 0x0e, 0x89, 0xd5, 0x3f, 0x9b, 0x4f, 0x2c, 0x6b, 0x4c, 0xf0, 0xa2, 0x15, 0x52, 0xe7, 0xa3, 0xe0, + 0xfd, 0xa6, 0x73, 0xc1, 0x4b, 0x44, 0xe4, 0x0a, 0x0c, 0xd6, 0xd8, 0xa4, 0xd2, 0xb4, 0x5b, 0x42, + 0x15, 0x30, 0x86, 0x5a, 0x47, 0xc0, 0xb4, 0x00, 0x4b, 0xae, 0x4b, 0xf9, 0x99, 0xa5, 0x60, 0xf6, + 0x7e, 0x7e, 0xe6, 0x78, 0x54, 0x77, 0xcc, 0xd4, 0x7c, 0x3d, 0x96, 0xef, 0x4d, 0x94, 0x49, 0x59, + 0x52, 0xc3, 0xfc, 0x6e, 0x81, 0x41, 0xdb, 0xbf, 0x93, 0x41, 0xab, 0xfe, 0x76, 0x2e, 0x39, 0x44, + 0xc9, 0x8d, 0x64, 0x5c, 0x69, 0x5c, 0xbf, 0x02, 0xa0, 0x5c, 0x6b, 0x10, 0x61, 0x3a, 0x12, 0x21, + 0x3a, 0xbf, 0xef, 0x08, 0xd1, 0x85, 0x3d, 0x46, 0x88, 0x56, 0xff, 0xdf, 0x62, 0x4f, 0x2f, 0xff, + 0x23, 0x89, 0x24, 0xf8, 0x1a, 0xdb, 0x94, 0xb1, 0xda, 0xcb, 0x6e, 0x62, 0x6b, 0xc1, 0x9d, 0x98, + 0x1b, 0x3a, 0x1f, 0x95, 0xae, 0x16, 0xa5, 0x24, 0x6f, 0xc3, 0x88, 0xff, 0x01, 0x18, 0x79, 0x5c, + 0x8a, 0x98, 0x1d, 0x2c, 0x88, 0xb1, 0x18, 0xdd, 0x91, 0x02, 0xe4, 0x65, 0x18, 0x42, 0x73, 0xa8, + 0xa3, 0x37, 0xfd, 0xb0, 0xf4, 0x3c, 0x8e, 0xbd, 0x0f, 0x94, 0xa3, 0xe5, 0x05, 0x94, 0xe4, 0x73, + 0x50, 0x12, 0xb9, 0x59, 0x4a, 0xb8, 0x44, 0x5f, 0xdb, 0xc5, 0xb3, 0x88, 0x29, 0x39, 0x2f, 0x0b, + 0xdf, 0xe0, 0x20, 0x20, 0xb2, 0xc1, 0xe1, 0x29, 0x59, 0x56, 0xe0, 0x74, 0xcd, 0xa1, 0x06, 0x3e, + 0xc0, 0x99, 0x7d, 0xd4, 0x71, 0x44, 0xd6, 0x1c, 0x3e, 0x41, 0xe0, 0xfa, 0xd6, 0xf1, 0xd1, 0x6c, + 0xe5, 0x15, 0x78, 0x39, 0x36, 0x76, 0x4a, 0x71, 0x66, 0xf4, 0xf0, 0x96, 0xdc, 0xa6, 0x9b, 0x1b, + 0xb6, 0x63, 0xf0, 0xc4, 0x32, 0x62, 0xea, 0x17, 0x82, 0x7e, 0x20, 0x50, 0xb2, 0xd1, 0x13, 0x2d, + 0x74, 0xf1, 0x35, 0x18, 0xde, 0x6f, 0x6e, 0x93, 0x5f, 0xce, 0x67, 0xbc, 0x97, 0x7b, 0x7c, 0xd3, + 0x4b, 0x06, 0x39, 0xcf, 0xfb, 0x33, 0x72, 0x9e, 0xff, 0x49, 0x3e, 0xe3, 0x31, 0xe0, 0x63, 0x9d, + 0x9b, 0x38, 0x10, 0x46, 0x34, 0x37, 0x71, 0x98, 0x16, 0xda, 0x34, 0x34, 0x99, 0x28, 0x96, 0xc5, + 0xbc, 0xb4, 0x63, 0x16, 0xf3, 0x9f, 0x2f, 0xf4, 0x7a, 0x2c, 0x79, 0x22, 0xfb, 0xbd, 0xc8, 0xfe, + 0x3a, 0x0c, 0x07, 0x92, 0xad, 0x56, 0xd0, 0x5e, 0x1a, 0x0d, 0x32, 0x29, 0x71, 0x30, 0x96, 0x91, + 0x88, 0xc8, 0x55, 0xde, 0xd6, 0xba, 0xf9, 0x3e, 0xcf, 0xe9, 0x31, 0x2a, 0xb2, 0x35, 0xe8, 0x9e, + 0xde, 0x70, 0xcd, 0xf7, 0xa9, 0x16, 0xa0, 0xd5, 0xbf, 0x9b, 0x4f, 0x7d, 0x71, 0x7a, 0xd2, 0x47, + 0x7b, 0xe8, 0xa3, 0x14, 0x21, 0xf2, 0xb7, 0xb2, 0x27, 0x42, 0xdc, 0x83, 0x10, 0xff, 0x38, 0x9f, + 0xfa, 0xb2, 0xf8, 0x44, 0x88, 0x7b, 0x99, 0x2d, 0x9e, 0x87, 0x21, 0xcd, 0xde, 0x70, 0x67, 0x70, + 0x4f, 0xc4, 0xe7, 0x0a, 0x9c, 0xa8, 0x1d, 0x7b, 0xc3, 0x6d, 0xe0, 0x6e, 0x47, 0x0b, 0x09, 0xd4, + 0xef, 0xe6, 0x7b, 0xbc, 0xbd, 0x3e, 0x11, 0xfc, 0x07, 0xb9, 0x44, 0xfe, 0x5a, 0x3e, 0xf2, 0xb6, + 0xfb, 0xf1, 0x15, 0xf6, 0x35, 0x80, 0x7a, 0x73, 0x9d, 0xb6, 0x75, 0x29, 0x2f, 0x1a, 0x1e, 0x59, + 0xb8, 0x08, 0x15, 0xf9, 0xb4, 0x43, 0x12, 0xf5, 0x1b, 0xf9, 0xd8, 0xe3, 0xf6, 0x13, 0xd9, 0xed, + 0x5a, 0x76, 0x81, 0xd6, 0x89, 0xf7, 0xfa, 0x27, 0x92, 0xdb, 0xad, 0xe4, 0x7e, 0x34, 0x1f, 0x0b, + 0x6d, 0xf0, 0xd8, 0xca, 0x8e, 0x0d, 0xc0, 0x64, 0xc8, 0x85, 0xc7, 0x56, 0x93, 0x9e, 0x87, 0x21, + 0x21, 0x87, 0x60, 0xa9, 0xe0, 0xf3, 0x3e, 0x07, 0xe2, 0x01, 0x6d, 0x40, 0xa0, 0xfe, 0xb9, 0x3c, + 0x44, 0x43, 0x4e, 0x3c, 0xa6, 0x3a, 0xf4, 0x6b, 0xf9, 0x68, 0xb0, 0x8d, 0xc7, 0x57, 0x7f, 0xa6, + 0x00, 0xea, 0xdd, 0xd5, 0xa6, 0x88, 0xd5, 0xdc, 0x2f, 0x9d, 0xf0, 0x07, 0x50, 0x4d, 0xa2, 0x50, + 0xff, 0x55, 0x3e, 0x35, 0x02, 0xc8, 0xe3, 0x2b, 0xc0, 0x97, 0xf0, 0x54, 0xbc, 0x69, 0x85, 0x13, + 0x39, 0x1e, 0x42, 0xb2, 0xf1, 0x97, 0x48, 0xa6, 0xe9, 0x13, 0x92, 0x4f, 0xa6, 0x98, 0x6b, 0x98, + 0xea, 0x23, 0x34, 0xd7, 0xe4, 0xc3, 0x7c, 0xc9, 0x70, 0xfb, 0xdd, 0xfc, 0x4e, 0x01, 0x53, 0x1e, + 0xe7, 0x55, 0x75, 0xa0, 0xa6, 0x6f, 0x62, 0x60, 0x4f, 0xd6, 0x13, 0x23, 0x3c, 0xd5, 0x63, 0x87, + 0x83, 0xe4, 0x6b, 0x3b, 0x41, 0xa5, 0xfe, 0xf3, 0xfe, 0xf4, 0x68, 0x1d, 0x8f, 0xaf, 0x08, 0x2f, + 0x41, 0xb1, 0xa6, 0x7b, 0xeb, 0x42, 0x93, 0xf1, 0x36, 0xb0, 0xa3, 0x7b, 0xeb, 0x1a, 0x42, 0xc9, + 0x55, 0x18, 0xd4, 0xf4, 0x0d, 0x7e, 0xe6, 0x59, 0x0a, 0xd3, 0x70, 0x3a, 0xfa, 0x46, 0x83, 0x9f, + 0x7b, 0x06, 0x68, 0xa2, 0x06, 0x69, 0x60, 0xf9, 0xc9, 0x37, 0xe6, 0x20, 0xe4, 0x69, 0x60, 0x83, + 0xe4, 0xaf, 0x97, 0xa0, 0x38, 0x6d, 0x1b, 0x9b, 0x78, 0xf3, 0x35, 0xc2, 0x2b, 0x5b, 0xb5, 0x8d, + 0x4d, 0x0d, 0xa1, 0xe4, 0xc7, 0x72, 0x30, 0x30, 0x4f, 0x75, 0x83, 0x8d, 0x90, 0xa1, 0x5e, 0x0e, + 0x2b, 0x9f, 0x39, 0x1c, 0x87, 0x95, 0x53, 0xeb, 0xbc, 0x32, 0x59, 0x51, 0x44, 0xfd, 0xe4, 0x26, + 0x0c, 0xce, 0xe8, 0x1e, 0x5d, 0xb3, 0x9d, 0x4d, 0x74, 0xc1, 0x19, 0x0b, 0x5f, 0x7c, 0x44, 0xf4, + 0xc7, 0x27, 0xe2, 0x37, 0x63, 0x4d, 0xf1, 0x4b, 0x0b, 0x0a, 0x33, 0xb1, 0xf0, 0x9b, 0x39, 0x91, + 0xf2, 0x1c, 0xc5, 0xc2, 0xaf, 0xf0, 0x34, 0x81, 0x09, 0x8f, 0x95, 0x47, 0xd2, 0x8f, 0x95, 0xd1, + 0x7a, 0x44, 0x37, 0x3d, 0x4c, 0xbe, 0x3a, 0x8a, 0x8b, 0x3e, 0xb7, 0x1e, 0x11, 0x8a, 0xb9, 0x57, + 0x35, 0x89, 0x44, 0xfd, 0x4e, 0x3f, 0xa4, 0xbe, 0xed, 0x3f, 0x51, 0xf2, 0x13, 0x25, 0x0f, 0x95, + 0xbc, 0x92, 0x50, 0xf2, 0x8b, 0xc9, 0x68, 0x11, 0x1f, 0x52, 0x0d, 0xff, 0x6a, 0x31, 0x11, 0x6b, + 0xe6, 0xf1, 0xde, 0x5d, 0x86, 0xd2, 0xeb, 0xdf, 0x51, 0x7a, 0xc1, 0x80, 0x28, 0xed, 0x38, 0x20, + 0x06, 0x76, 0x3b, 0x20, 0x06, 0x33, 0x07, 0x44, 0xa8, 0x20, 0x43, 0x99, 0x0a, 0x52, 0x15, 0x83, + 0x06, 0x7a, 0xa7, 0xbc, 0xb9, 0xb4, 0xbd, 0x35, 0x39, 0xc6, 0x46, 0x53, 0x6a, 0xae, 0x1b, 0x64, + 0xa1, 0xfe, 0x5e, 0xb1, 0x47, 0x80, 0xa8, 0x23, 0xd1, 0x91, 0x97, 0xa0, 0x50, 0xee, 0x74, 0x84, + 0x7e, 0x9c, 0x96, 0x62, 0x53, 0x65, 0x94, 0x62, 0xd4, 0xe4, 0x75, 0x28, 0x94, 0xef, 0xd5, 0xe3, + 0x69, 0x6e, 0xca, 0xf7, 0xea, 0xe2, 0x4b, 0x32, 0xcb, 0xde, 0xab, 0x93, 0x37, 0xc3, 0x78, 0xb3, + 0xeb, 0x5d, 0xeb, 0x81, 0xd8, 0x28, 0x0a, 0x4f, 0x5d, 0xdf, 0x93, 0xa7, 0xc9, 0x50, 0x6c, 0xbb, + 0x18, 0xa3, 0x8d, 0x69, 0x53, 0x69, 0xf7, 0xda, 0x34, 0xb0, 0xa3, 0x36, 0x0d, 0xee, 0x56, 0x9b, + 0x86, 0x76, 0xa1, 0x4d, 0xb0, 0xa3, 0x36, 0x0d, 0x1f, 0x5c, 0x9b, 0x3a, 0x70, 0x31, 0x19, 0xd4, + 0x2f, 0xd0, 0x08, 0x0d, 0x48, 0x12, 0x2b, 0x1c, 0x4b, 0xf0, 0xea, 0xbf, 0xcb, 0xb1, 0x8d, 0x0d, + 0x44, 0x37, 0x5c, 0x86, 0x97, 0x5d, 0xdb, 0x92, 0xa5, 0xd5, 0x5f, 0xce, 0x67, 0xc7, 0x22, 0x3c, + 0x9e, 0x53, 0xdc, 0x0f, 0xa4, 0x4a, 0xa9, 0x18, 0x7b, 0x3c, 0x91, 0x29, 0xe5, 0x18, 0xdb, 0x34, + 0x99, 0x7d, 0x3d, 0x9f, 0x15, 0x20, 0xf1, 0x40, 0x12, 0xfb, 0x68, 0xd2, 0x19, 0x0e, 0x5d, 0xfc, + 0xdd, 0xa8, 0x17, 0xdc, 0x1c, 0x8c, 0xc8, 0x42, 0x14, 0x52, 0xda, 0x8d, 0x80, 0x23, 0xe5, 0xc8, + 0x9b, 0x41, 0x36, 0x22, 0xc9, 0x3f, 0x06, 0x3d, 0xdd, 0xfc, 0x31, 0x1b, 0x73, 0x8f, 0x91, 0xc9, + 0xc9, 0xf3, 0x50, 0x9a, 0xc3, 0xf0, 0xfe, 0xf2, 0x60, 0xe7, 0x01, 0xff, 0x65, 0xaf, 0x15, 0x4e, + 0xa3, 0xfe, 0x76, 0x0e, 0x4e, 0xdf, 0xee, 0xae, 0x52, 0xe1, 0x68, 0x17, 0xb4, 0xe1, 0x3d, 0x00, + 0x06, 0x16, 0x0e, 0x33, 0x39, 0x74, 0x98, 0xf9, 0xb8, 0x1c, 0x48, 0x31, 0x56, 0x60, 0x2a, 0xa4, + 0xe6, 0xce, 0x32, 0x4f, 0xfa, 0x3e, 0xa7, 0x0f, 0xba, 0xab, 0xb4, 0x91, 0xf0, 0x9a, 0x91, 0xb8, + 0x5f, 0x7c, 0x8b, 0x7b, 0xf3, 0xef, 0xd7, 0x41, 0xe5, 0x97, 0xf2, 0x99, 0xb1, 0x2b, 0x8f, 0x6d, + 0x92, 0xd5, 0xef, 0x4b, 0xed, 0x95, 0x78, 0xb2, 0xd5, 0x14, 0x92, 0x18, 0xc7, 0x34, 0x2e, 0xe9, + 0x02, 0x3b, 0xe6, 0xa9, 0x7f, 0x3f, 0x50, 0x81, 0xfd, 0x61, 0x2e, 0x33, 0xc6, 0xe8, 0x71, 0x15, + 0x98, 0xfa, 0xbf, 0x14, 0xfc, 0xd0, 0xa6, 0x07, 0xfa, 0x84, 0xe7, 0x61, 0x48, 0x44, 0x78, 0x88, + 0xfa, 0x09, 0x8b, 0x63, 0x43, 0x3c, 0x86, 0x0e, 0x08, 0x98, 0x49, 0x21, 0x39, 0x31, 0x4b, 0x7e, + 0xc2, 0x92, 0x03, 0xb3, 0x26, 0x91, 0x30, 0xa3, 0x61, 0xf6, 0x91, 0xe9, 0xa1, 0x05, 0xc2, 0xfa, + 0xb2, 0xc0, 0x8d, 0x06, 0xfa, 0xc8, 0xf4, 0xb8, 0xfd, 0x11, 0xa0, 0x99, 0x41, 0xc0, 0x6d, 0x11, + 0x31, 0xef, 0xa1, 0x41, 0xc0, 0x4d, 0x15, 0x4d, 0x60, 0x58, 0x6b, 0x85, 0xf3, 0xad, 0x70, 0x69, + 0x11, 0xad, 0x15, 0xee, 0xba, 0xd8, 0xda, 0x80, 0x80, 0x71, 0xd4, 0xe8, 0x5a, 0xe8, 0xc4, 0x87, + 0x1c, 0x1d, 0x84, 0x68, 0x02, 0x43, 0xae, 0xc3, 0x58, 0xdd, 0xd3, 0x2d, 0x43, 0x77, 0x8c, 0xe5, + 0xae, 0xd7, 0xe9, 0x7a, 0xb2, 0x01, 0xec, 0x7a, 0x86, 0xdd, 0xf5, 0xb4, 0x18, 0x05, 0xf9, 0x04, + 0x8c, 0xfa, 0x90, 0x59, 0xc7, 0xb1, 0x1d, 0xd9, 0xca, 0x71, 0x3d, 0x83, 0x3a, 0x8e, 0x16, 0x25, + 0x20, 0x9f, 0x84, 0xd1, 0xaa, 0xf5, 0xd0, 0x6e, 0xf2, 0x28, 0x07, 0xda, 0x82, 0xb0, 0x79, 0xf0, + 0xc5, 0x98, 0x19, 0x20, 0x1a, 0x5d, 0xa7, 0xa5, 0x45, 0x09, 0xd5, 0xed, 0x7c, 0x32, 0x02, 0xec, + 0xe3, 0xbb, 0x41, 0xba, 0x1a, 0x75, 0xdc, 0x43, 0x6f, 0x55, 0x34, 0x3e, 0x65, 0xbf, 0x61, 0x6e, + 0x83, 0x5e, 0x87, 0xc1, 0xdb, 0x74, 0x93, 0xfb, 0x98, 0x96, 0x42, 0xb7, 0xe4, 0x07, 0x02, 0x26, + 0x9f, 0xee, 0xfa, 0x74, 0xea, 0x37, 0xf3, 0xc9, 0xd8, 0xb6, 0x8f, 0xaf, 0xb0, 0x3f, 0x01, 0x03, + 0x28, 0xca, 0xaa, 0x7f, 0xbd, 0x80, 0x02, 0x44, 0x71, 0x47, 0xbd, 0x9d, 0x7d, 0x32, 0xf5, 0xe7, + 0x4a, 0xf1, 0x80, 0xc7, 0x8f, 0xaf, 0xf4, 0xde, 0x80, 0xe1, 0x19, 0xdb, 0x72, 0x4d, 0xd7, 0xa3, + 0x56, 0xd3, 0x57, 0x58, 0x74, 0xfc, 0x6f, 0x86, 0x60, 0xd9, 0x06, 0x94, 0xa8, 0xf7, 0xa3, 0xbc, + 0xe4, 0x15, 0x18, 0x42, 0x91, 0xa3, 0xcd, 0xc9, 0x27, 0x3c, 0xbc, 0x99, 0x58, 0x65, 0xc0, 0xb8, + 0xc5, 0x19, 0x92, 0x92, 0x3b, 0x30, 0x38, 0xb3, 0x6e, 0xb6, 0x0c, 0x87, 0x5a, 0xe8, 0x9b, 0x2c, + 0xc5, 0x95, 0x89, 0xf6, 0xe5, 0x14, 0xfe, 0x8b, 0xb4, 0xbc, 0x39, 0x4d, 0x51, 0x2c, 0xf2, 0x58, + 0x4c, 0xc0, 0x2e, 0xfe, 0x54, 0x1e, 0x20, 0x2c, 0x40, 0x9e, 0x86, 0x7c, 0x90, 0x33, 0x1c, 0x5d, + 0x62, 0x22, 0x1a, 0x94, 0xc7, 0xa5, 0x42, 0x8c, 0xed, 0xfc, 0x8e, 0x63, 0xfb, 0x0e, 0x94, 0xf8, + 0xe9, 0x1a, 0x7a, 0xad, 0x4b, 0x31, 0x58, 0x33, 0x1b, 0x3c, 0x85, 0xf4, 0xdc, 0x96, 0x46, 0xcb, + 0x33, 0xe2, 0x01, 0xce, 0x99, 0x5d, 0x6c, 0x42, 0x3f, 0xfe, 0x45, 0x2e, 0x43, 0x71, 0xc5, 0xcf, + 0x37, 0x3c, 0xca, 0x67, 0xe9, 0x98, 0xfc, 0x10, 0xcf, 0xba, 0x69, 0xc6, 0xb6, 0x3c, 0x56, 0x35, + 0xb6, 0x7a, 0x44, 0xc8, 0x45, 0xc0, 0x22, 0x72, 0x11, 0x30, 0xf5, 0xbf, 0xca, 0xa7, 0x84, 0xe2, + 0x7e, 0x7c, 0x87, 0xc9, 0x6b, 0x00, 0xf8, 0xf2, 0x9c, 0xc9, 0xd3, 0x7f, 0x0e, 0x82, 0xa3, 0x04, + 0x19, 0xa1, 0xda, 0x46, 0xb6, 0x1d, 0x21, 0xb1, 0xfa, 0x0f, 0x72, 0x89, 0xf8, 0xcd, 0x07, 0x92, + 0xa3, 0x6c, 0x95, 0xe5, 0xf7, 0x69, 0xc6, 0xfa, 0x7d, 0x51, 0xd8, 0x5b, 0x5f, 0x44, 0xbf, 0xe5, + 0x10, 0x2c, 0xd3, 0xa3, 0xfc, 0x96, 0xef, 0xe4, 0xd3, 0xa2, 0x59, 0x1f, 0x4f, 0x15, 0xbf, 0x11, + 0x18, 0xa5, 0xc5, 0x58, 0xfe, 0x00, 0x84, 0xc6, 0x73, 0xa2, 0x0b, 0x33, 0xf5, 0xf3, 0x30, 0x1e, + 0x8b, 0xf1, 0x2c, 0xd2, 0x53, 0x5f, 0xee, 0x1d, 0x2c, 0x3a, 0x3b, 0x66, 0x41, 0x84, 0x4c, 0xfd, + 0xff, 0x72, 0xbd, 0x23, 0x7c, 0x1f, 0xb9, 0xea, 0xa4, 0x08, 0xa0, 0xf0, 0xaf, 0x47, 0x00, 0x87, + 0xb0, 0x0d, 0x3e, 0xde, 0x02, 0xf8, 0x90, 0x4c, 0x1e, 0x1f, 0xb4, 0x00, 0x7e, 0x2e, 0xb7, 0x63, + 0x80, 0xf6, 0xa3, 0x96, 0x81, 0xfa, 0x3f, 0xe6, 0x52, 0x03, 0xa9, 0x1f, 0xa8, 0x5d, 0x6f, 0x42, + 0x89, 0xbb, 0xf0, 0x88, 0x56, 0x49, 0xa9, 0xe7, 0x18, 0x34, 0xa3, 0xbc, 0x28, 0x43, 0x16, 0x60, + 0x80, 0xb7, 0xc1, 0x10, 0xbd, 0xf1, 0x91, 0x1e, 0xd1, 0xdc, 0x8d, 0xac, 0xc9, 0x51, 0xa0, 0xd5, + 0xdf, 0xc9, 0x25, 0xe2, 0xba, 0x1f, 0xe1, 0xb7, 0x85, 0x53, 0x75, 0x61, 0xf7, 0x53, 0xb5, 0xfa, + 0xcf, 0xf2, 0xe9, 0x61, 0xe5, 0x8f, 0xf0, 0x43, 0x0e, 0xe3, 0x38, 0x6d, 0x7f, 0xeb, 0xd6, 0x0a, + 0x8c, 0x45, 0x65, 0x21, 0x96, 0xad, 0xa7, 0xd2, 0x83, 0xeb, 0x67, 0xb4, 0x22, 0xc6, 0x43, 0xfd, + 0x76, 0x2e, 0x19, 0x11, 0xff, 0xc8, 0xe7, 0xa7, 0xfd, 0x69, 0x4b, 0xf4, 0x53, 0x3e, 0x24, 0x6b, + 0xcd, 0x61, 0x7c, 0xca, 0x87, 0x64, 0xd5, 0xd8, 0xdf, 0xa7, 0xfc, 0x42, 0x3e, 0x2b, 0xa1, 0xc0, + 0x91, 0x7f, 0xd0, 0x67, 0x65, 0x21, 0xf3, 0x96, 0x89, 0x4f, 0x7b, 0x3a, 0x2b, 0x82, 0x7f, 0x06, + 0xcf, 0x04, 0x9f, 0xfd, 0x8d, 0xf1, 0x54, 0x61, 0x7d, 0x48, 0x14, 0xf9, 0x78, 0x08, 0xeb, 0x43, + 0x32, 0x54, 0x3e, 0x7c, 0xc2, 0xfa, 0x8d, 0xfc, 0x6e, 0xb3, 0x58, 0x9c, 0x08, 0x2f, 0x21, 0xbc, + 0x2f, 0xe7, 0x93, 0xd9, 0x55, 0x8e, 0x5c, 0x4c, 0x73, 0x50, 0x12, 0x79, 0x5e, 0x32, 0x85, 0xc3, + 0xf1, 0x59, 0x16, 0x8d, 0xf8, 0x8e, 0x1b, 0x20, 0x2e, 0x72, 0x76, 0x27, 0x12, 0x4e, 0xab, 0x7e, + 0x37, 0x17, 0x4b, 0x45, 0x72, 0x24, 0x47, 0x08, 0xfb, 0x5a, 0x92, 0xc8, 0x5b, 0xfe, 0x61, 0x66, + 0x31, 0x16, 0x0a, 0x3e, 0xf8, 0x9e, 0x0a, 0xf5, 0x74, 0xb3, 0x15, 0x2f, 0x2f, 0xe2, 0x0f, 0x7c, + 0x33, 0x0f, 0xa7, 0x12, 0xa4, 0xe4, 0x72, 0x24, 0xe2, 0x0f, 0x1e, 0x4b, 0xc6, 0x1c, 0xd5, 0x79, + 0xec, 0x9f, 0x3d, 0x9c, 0xa4, 0x5e, 0x86, 0x62, 0x45, 0xdf, 0xe4, 0xdf, 0xd6, 0xcf, 0x59, 0x1a, + 0xfa, 0xa6, 0x7c, 0xe2, 0x86, 0x78, 0xb2, 0x0a, 0x67, 0xf9, 0x7d, 0x88, 0x69, 0x5b, 0x2b, 0x66, + 0x9b, 0x56, 0xad, 0x45, 0xb3, 0xd5, 0x32, 0x5d, 0x71, 0xa9, 0xf7, 0xfc, 0xf6, 0xd6, 0xe4, 0x15, + 0xcf, 0xf6, 0xf4, 0x56, 0x83, 0xfa, 0x64, 0x0d, 0xcf, 0x6c, 0xd3, 0x86, 0x69, 0x35, 0xda, 0x48, + 0x29, 0xb1, 0x4c, 0x67, 0x45, 0xaa, 0x3c, 0xea, 0x7f, 0xbd, 0xa9, 0x5b, 0x16, 0x35, 0xaa, 0xd6, + 0xf4, 0xa6, 0x47, 0xf9, 0x65, 0x60, 0x81, 0x1f, 0x09, 0xf2, 0x77, 0xe8, 0x1c, 0xcd, 0x18, 0xaf, + 0x32, 0x02, 0x2d, 0xa5, 0x90, 0xfa, 0x9b, 0xc5, 0x94, 0x2c, 0x34, 0xc7, 0x48, 0x7d, 0xfc, 0x9e, + 0x2e, 0xee, 0xd0, 0xd3, 0xd7, 0x60, 0x40, 0x84, 0x55, 0x16, 0x17, 0x0c, 0xe8, 0x38, 0xff, 0x90, + 0x83, 0xe4, 0x1b, 0x1a, 0x41, 0x45, 0x5a, 0x70, 0x71, 0x85, 0x75, 0x53, 0x7a, 0x67, 0x96, 0xf6, + 0xd1, 0x99, 0x3d, 0xf8, 0x91, 0x77, 0xe1, 0x3c, 0x62, 0x53, 0xba, 0x75, 0x00, 0xab, 0xc2, 0x50, + 0x5a, 0xbc, 0xaa, 0xf4, 0xce, 0xcd, 0x2a, 0x4f, 0x3e, 0x0b, 0x23, 0xc1, 0x00, 0x31, 0xa9, 0x2b, + 0x6e, 0x2e, 0x7a, 0x8c, 0x33, 0x1e, 0xa7, 0x8e, 0x81, 0xd1, 0x5d, 0x2d, 0x1a, 0xeb, 0x2c, 0xc2, + 0x4b, 0xfd, 0x1f, 0x72, 0xbd, 0xb2, 0xe1, 0x1c, 0xf9, 0xac, 0xfc, 0x16, 0x0c, 0x18, 0xfc, 0xa3, + 0x84, 0x4e, 0xf5, 0xce, 0x97, 0xc3, 0x49, 0x35, 0xbf, 0x8c, 0xfa, 0x4f, 0x73, 0x3d, 0x93, 0xf0, + 0x1c, 0xf7, 0xcf, 0xfb, 0x72, 0x21, 0xe3, 0xf3, 0xc4, 0x24, 0x7a, 0x15, 0x26, 0xcc, 0x30, 0x4b, + 0x40, 0x23, 0x0c, 0x75, 0xa5, 0x8d, 0x4b, 0x70, 0x1c, 0x5d, 0x37, 0x20, 0x70, 0xd8, 0x72, 0x7c, + 0x6f, 0x34, 0xb7, 0xd1, 0x75, 0x4c, 0x3e, 0x2e, 0xb5, 0x33, 0x6e, 0xcc, 0x55, 0xcd, 0xbd, 0xe3, + 0x98, 0xac, 0x02, 0xdd, 0x5b, 0xa7, 0x96, 0xde, 0xd8, 0xb0, 0x9d, 0x07, 0x18, 0x0c, 0x95, 0x0f, + 0x4e, 0x6d, 0x9c, 0xc3, 0xef, 0xf9, 0x60, 0xf2, 0x2c, 0x8c, 0xae, 0xb5, 0xba, 0x34, 0x08, 0x3f, + 0xc9, 0xef, 0xfa, 0xb4, 0x11, 0x06, 0x0c, 0x6e, 0x48, 0x9e, 0x04, 0x40, 0x22, 0x0f, 0x53, 0x24, + 0xe1, 0xc5, 0x9e, 0x36, 0xc4, 0x20, 0x2b, 0xa2, 0xbb, 0x2e, 0x72, 0xad, 0xe6, 0x42, 0x6a, 0xb4, + 0x6c, 0x6b, 0xad, 0xe1, 0x51, 0xa7, 0x8d, 0x0d, 0x45, 0x67, 0x06, 0xed, 0x1c, 0x52, 0xe0, 0xd5, + 0x89, 0xbb, 0x60, 0x5b, 0x6b, 0x2b, 0xd4, 0x69, 0xb3, 0xa6, 0x3e, 0x0f, 0x44, 0x34, 0xd5, 0xc1, + 0x43, 0x0f, 0xfe, 0x71, 0xe8, 0xcd, 0xa0, 0x89, 0x8f, 0xe0, 0xa7, 0x21, 0xf8, 0x61, 0x93, 0x30, + 0xcc, 0x63, 0xf0, 0x71, 0xa1, 0xa1, 0x0b, 0x83, 0x06, 0x1c, 0x84, 0xf2, 0x3a, 0x07, 0xc2, 0xbb, + 0x82, 0x7b, 0x90, 0x6b, 0xe2, 0x97, 0xfa, 0xc5, 0x42, 0x5a, 0xde, 0xa0, 0x03, 0x29, 0x5a, 0x38, + 0xad, 0xe6, 0xf7, 0x34, 0xad, 0x8e, 0x5b, 0xdd, 0x76, 0x43, 0xef, 0x74, 0x1a, 0xf7, 0xcd, 0x16, + 0x3e, 0xe1, 0xc2, 0x85, 0x4f, 0x1b, 0xb5, 0xba, 0xed, 0x72, 0xa7, 0x33, 0xc7, 0x81, 0xe4, 0x39, + 0x38, 0xc5, 0xe8, 0xb0, 0x93, 0x02, 0xca, 0x22, 0x52, 0x32, 0x06, 0x18, 0xc4, 0xd6, 0xa7, 0xbd, + 0x00, 0x83, 0x82, 0x27, 0x5f, 0xab, 0xfa, 0xb5, 0x01, 0xce, 0xcc, 0x65, 0x3d, 0x17, 0xb0, 0xe1, + 0x93, 0x6b, 0xbf, 0x36, 0xe4, 0x97, 0xc7, 0x50, 0xcd, 0x56, 0xb7, 0xcd, 0xa3, 0x6f, 0x0d, 0x20, + 0x32, 0xf8, 0x4d, 0x2e, 0xc3, 0x18, 0xe3, 0x12, 0x08, 0x8c, 0x47, 0xb7, 0xed, 0xd7, 0x62, 0x50, + 0x72, 0x1d, 0xce, 0x44, 0x20, 0xdc, 0x06, 0xe5, 0x4f, 0x12, 0xfa, 0xb5, 0x54, 0x9c, 0xfa, 0x8d, + 0x42, 0x34, 0x9b, 0xd1, 0x11, 0x74, 0xc4, 0x79, 0x18, 0xb0, 0x9d, 0xb5, 0x46, 0xd7, 0x69, 0x89, + 0xb1, 0x57, 0xb2, 0x9d, 0xb5, 0x3b, 0x4e, 0x8b, 0x9c, 0x85, 0x12, 0xeb, 0x1d, 0xd3, 0x10, 0x43, + 0xac, 0x5f, 0xef, 0x74, 0xaa, 0x06, 0x29, 0xf3, 0x0e, 0xc1, 0xc8, 0xa8, 0x8d, 0x26, 0x6e, 0xed, + 0xb9, 0x53, 0x42, 0x3f, 0x5f, 0xf1, 0x12, 0x48, 0xec, 0x27, 0x8c, 0x97, 0xca, 0x0f, 0x02, 0x62, + 0x2c, 0x0c, 0xdc, 0x96, 0x18, 0xbc, 0x4f, 0xe2, 0x2c, 0x04, 0x32, 0x64, 0xc1, 0x37, 0x31, 0x06, + 0xa9, 0x00, 0x09, 0xa9, 0xda, 0xb6, 0x61, 0xde, 0x37, 0x29, 0x7f, 0x41, 0xd2, 0xcf, 0x2f, 0x7e, + 0x93, 0x58, 0x6d, 0xc2, 0x67, 0xb2, 0x28, 0x20, 0xe4, 0x0d, 0xae, 0x84, 0x9c, 0x0e, 0xd7, 0x3e, + 0xde, 0xb7, 0xdc, 0x4e, 0x8b, 0xa1, 0x50, 0x33, 0xb1, 0x3c, 0x2e, 0x84, 0xea, 0x7f, 0xd7, 0x9f, + 0x4c, 0x69, 0x75, 0x24, 0x76, 0xcd, 0x3c, 0x80, 0xc8, 0x58, 0x17, 0x5e, 0xae, 0x5d, 0x94, 0xc2, + 0xd3, 0x0b, 0x4c, 0x06, 0x0f, 0xa9, 0x2c, 0xb9, 0x0a, 0x83, 0xfc, 0x8b, 0xaa, 0x15, 0x61, 0xef, + 0xa0, 0x8b, 0x98, 0xdb, 0x31, 0xef, 0xdf, 0x47, 0x7f, 0xb2, 0x00, 0x4d, 0x2e, 0xc3, 0x40, 0x65, + 0xa9, 0x5e, 0x2f, 0x2f, 0xf9, 0x37, 0xc5, 0xf8, 0x96, 0xc5, 0xb0, 0xdc, 0x86, 0xab, 0x5b, 0xae, + 0xe6, 0x23, 0xc9, 0xb3, 0x50, 0xaa, 0xd6, 0x90, 0x8c, 0xbf, 0xd0, 0x1c, 0xde, 0xde, 0x9a, 0x1c, + 0x30, 0x3b, 0x9c, 0x4a, 0xa0, 0xb0, 0xde, 0xbb, 0xd5, 0x8a, 0xe4, 0x2e, 0xc1, 0xeb, 0x7d, 0x68, + 0x1a, 0x78, 0xed, 0xac, 0x05, 0x68, 0xf2, 0x32, 0x8c, 0xd4, 0xa9, 0x63, 0xea, 0xad, 0xa5, 0x2e, + 0x6e, 0x15, 0xa5, 0x88, 0x8f, 0x2e, 0xc2, 0x1b, 0x16, 0x22, 0xb4, 0x08, 0x19, 0xb9, 0x04, 0xc5, + 0x79, 0xd3, 0xf2, 0x9f, 0x4b, 0xa0, 0x3f, 0xfd, 0xba, 0x69, 0x79, 0x1a, 0x42, 0xc9, 0xb3, 0x50, + 0xb8, 0xb5, 0x52, 0x15, 0x9e, 0x60, 0xc8, 0xeb, 0x3d, 0x2f, 0x12, 0x3d, 0xf2, 0xd6, 0x4a, 0x95, + 0xbc, 0x0c, 0x43, 0x6c, 0x11, 0xa3, 0x56, 0x93, 0xba, 0xca, 0x30, 0x7e, 0x0c, 0x0f, 0x59, 0xe8, + 0x03, 0x65, 0x9f, 0x8e, 0x80, 0x92, 0xdc, 0x86, 0x89, 0x78, 0x24, 0x7c, 0xf1, 0x64, 0x07, 0x2d, + 0xae, 0x0d, 0x81, 0x4b, 0x0b, 0x9a, 0x99, 0x28, 0x48, 0x0c, 0x50, 0xe2, 0x30, 0xb6, 0xaf, 0x43, + 0xab, 0x93, 0xc7, 0x6b, 0xbe, 0xb2, 0xbd, 0x35, 0xf9, 0x91, 0x04, 0xd3, 0x86, 0x23, 0xa8, 0x24, + 0xee, 0x99, 0x9c, 0xd4, 0xff, 0x33, 0x9f, 0x9e, 0x26, 0xed, 0x08, 0x66, 0xa7, 0x7d, 0x5e, 0x7c, + 0xc7, 0xc6, 0x44, 0xf1, 0x00, 0x63, 0xe2, 0x3e, 0x8c, 0x97, 0x8d, 0xb6, 0x69, 0x95, 0xf1, 0xa7, + 0xbb, 0x38, 0x57, 0xc6, 0xd9, 0x4e, 0x7a, 0xbd, 0x18, 0x43, 0x8b, 0xef, 0xe1, 0xa1, 0x94, 0x19, + 0xaa, 0xa1, 0x73, 0x5c, 0xa3, 0x7d, 0x5f, 0x6f, 0x34, 0x79, 0x86, 0x31, 0x2d, 0xce, 0x54, 0xfd, + 0xc9, 0xfc, 0x0e, 0x99, 0xdd, 0x1e, 0x47, 0xe9, 0xab, 0x5f, 0xc9, 0xf7, 0x4e, 0xae, 0xf7, 0x58, + 0x0a, 0xe5, 0x8f, 0xf3, 0x29, 0xa9, 0xee, 0x0e, 0x24, 0x89, 0xab, 0x30, 0xc8, 0xd9, 0x04, 0x9e, + 0xc7, 0x38, 0x01, 0x73, 0x65, 0xc5, 0x89, 0xdf, 0x47, 0x93, 0x25, 0x38, 0x53, 0xbe, 0x7f, 0x9f, + 0x36, 0xbd, 0x30, 0xa8, 0xf6, 0x52, 0x18, 0xa3, 0x96, 0x07, 0x11, 0x16, 0xf8, 0x30, 0x28, 0x37, + 0xc6, 0x62, 0x49, 0x2d, 0x47, 0x56, 0xe0, 0x5c, 0x1c, 0x5e, 0xe7, 0xbb, 0x96, 0xa2, 0x14, 0x57, + 0x38, 0xc1, 0x91, 0xff, 0xa7, 0x65, 0x94, 0x4d, 0x6b, 0x25, 0xae, 0x2e, 0xfd, 0xbd, 0x5a, 0x89, + 0x4b, 0x4d, 0x6a, 0x39, 0xf5, 0x9b, 0x05, 0x39, 0x23, 0xe0, 0xe3, 0xeb, 0x23, 0x76, 0x23, 0xe2, + 0x19, 0xbe, 0xdb, 0x21, 0xf3, 0xb2, 0x08, 0xb0, 0x62, 0x74, 0x1d, 0xdf, 0x89, 0x32, 0x08, 0xf0, + 0x80, 0x40, 0x79, 0xe9, 0x0c, 0x28, 0x49, 0x15, 0x8a, 0x65, 0x67, 0x8d, 0x5b, 0xe4, 0x3b, 0xbd, + 0x39, 0xd3, 0x9d, 0x35, 0x37, 0xfd, 0xcd, 0x19, 0x63, 0xa1, 0xfe, 0xc5, 0x7c, 0x8f, 0x24, 0x7e, + 0x8f, 0xe5, 0x24, 0xf2, 0xb3, 0xf9, 0xac, 0x74, 0x7c, 0xc7, 0xd5, 0xdb, 0xed, 0x03, 0x16, 0xce, + 0xf1, 0x76, 0x05, 0x3c, 0x44, 0xe1, 0xfc, 0x41, 0x3e, 0x2b, 0xb7, 0xe0, 0x89, 0x70, 0xf6, 0x37, + 0x41, 0xa6, 0x8a, 0xf4, 0x31, 0xb6, 0xb9, 0x65, 0x55, 0xe8, 0xdf, 0xa7, 0xc7, 0x57, 0x9a, 0x48, + 0x4f, 0x86, 0xf0, 0x81, 0xb4, 0xf4, 0x0f, 0xf3, 0x99, 0x39, 0x34, 0x4f, 0x64, 0x7a, 0x98, 0x32, + 0x3d, 0x19, 0xfa, 0x07, 0x1a, 0xfa, 0xa9, 0x32, 0x3d, 0x19, 0xfb, 0x07, 0xd2, 0xd3, 0xdf, 0xcf, + 0xa7, 0x67, 0x89, 0x3d, 0x02, 0x25, 0x3d, 0x0c, 0xa7, 0x4c, 0xbf, 0x1b, 0x8a, 0x07, 0xea, 0x86, + 0xfe, 0x03, 0x58, 0x51, 0x49, 0x81, 0x1e, 0xd9, 0xa8, 0xff, 0x5e, 0x15, 0xe8, 0x21, 0x0c, 0xf9, + 0xc7, 0x59, 0xa0, 0x3f, 0x5e, 0x48, 0x66, 0x46, 0x7e, 0x5c, 0xd7, 0x24, 0x67, 0x9f, 0x6b, 0x92, + 0x5f, 0x8e, 0xbc, 0x0d, 0xe3, 0xa1, 0x2c, 0xe5, 0xc0, 0x68, 0x78, 0xe3, 0xd5, 0x64, 0xa8, 0xc6, + 0x7b, 0x0c, 0x27, 0x22, 0xf8, 0xc4, 0xa9, 0xd5, 0xef, 0x16, 0x92, 0xe9, 0xa5, 0x4f, 0x7a, 0x63, + 0x9f, 0xbd, 0x71, 0x07, 0xce, 0xcd, 0x74, 0x1d, 0x87, 0x5a, 0x5e, 0x7a, 0xa7, 0xe0, 0xe1, 0x7d, + 0x93, 0x53, 0x34, 0x92, 0x9d, 0x93, 0x51, 0x98, 0xb1, 0x15, 0x0f, 0x32, 0xe2, 0x6c, 0x07, 0x42, + 0xb6, 0x5d, 0x4e, 0x91, 0xc6, 0x36, 0xbd, 0xb0, 0xfa, 0xbb, 0xf9, 0x64, 0x42, 0xf0, 0x93, 0xae, + 0xdf, 0x5f, 0xd7, 0xab, 0x5f, 0x2c, 0xc4, 0x93, 0xa2, 0x9f, 0x2c, 0x10, 0xfb, 0xef, 0x0e, 0x5f, + 0x92, 0x38, 0x6e, 0xa4, 0xaf, 0xf0, 0xe1, 0x59, 0x5f, 0xe1, 0xe3, 0xd5, 0x5f, 0x2a, 0xc6, 0x13, + 0xcc, 0x9f, 0x74, 0xc7, 0xd1, 0x75, 0x07, 0x59, 0x86, 0x33, 0x62, 0x6e, 0xf3, 0x41, 0x98, 0x21, + 0x43, 0xcc, 0x5f, 0x3c, 0xd1, 0x9e, 0x98, 0x16, 0xbb, 0x2e, 0x75, 0x1a, 0x9e, 0xee, 0x3e, 0x68, + 0x60, 0x4a, 0x0d, 0x2d, 0xb5, 0x20, 0x63, 0x28, 0x66, 0xb5, 0x28, 0xc3, 0xc1, 0x90, 0xa1, 0x3f, + 0x21, 0x26, 0x18, 0xa6, 0x15, 0x54, 0x7f, 0x2d, 0x07, 0x13, 0xf1, 0xcf, 0x21, 0x53, 0x30, 0xc8, + 0x7e, 0x07, 0x91, 0x02, 0xa4, 0x0c, 0xe0, 0x9c, 0x23, 0xf7, 0x22, 0xf0, 0x69, 0xc8, 0x2b, 0x30, + 0x84, 0x0e, 0x1b, 0x58, 0x20, 0x1f, 0x06, 0x68, 0x08, 0x0b, 0x60, 0x5a, 0x5a, 0x5e, 0x2c, 0x24, + 0x25, 0x6f, 0xc0, 0x70, 0x35, 0xf4, 0x4c, 0x13, 0x77, 0x5e, 0xe8, 0x10, 0x2b, 0x95, 0x0c, 0x09, + 0x34, 0x99, 0x5a, 0xfd, 0x76, 0x3e, 0x54, 0xf5, 0x13, 0xd3, 0xf4, 0x40, 0xa6, 0xe9, 0x57, 0x0b, + 0x70, 0x2e, 0xee, 0xbe, 0x70, 0x72, 0x10, 0x25, 0xe6, 0x81, 0x3f, 0x03, 0x67, 0xe2, 0xb2, 0xa9, + 0x30, 0x69, 0xf4, 0xf7, 0xbe, 0x46, 0x9b, 0xda, 0xde, 0x9a, 0x7c, 0x3a, 0xe9, 0x39, 0xc2, 0x2a, + 0x4b, 0xbd, 0x58, 0x4b, 0xad, 0x24, 0xb5, 0x67, 0x3e, 0x24, 0x6f, 0x9a, 0x1e, 0xf3, 0x9e, 0xf9, + 0xd9, 0x7c, 0xb2, 0x67, 0x4e, 0x0e, 0xc5, 0xc4, 0x84, 0xf2, 0x0f, 0x73, 0xfe, 0xfd, 0xf0, 0x82, + 0xe9, 0x7a, 0x55, 0xeb, 0xa1, 0xde, 0x32, 0x83, 0x47, 0xd7, 0xe4, 0xa6, 0x9f, 0x2e, 0x9d, 0x21, + 0xa5, 0x77, 0x1f, 0xe8, 0xc1, 0x25, 0xd2, 0xa5, 0xb7, 0x4c, 0x57, 0xe4, 0xb6, 0x7e, 0x3a, 0x91, + 0x30, 0xdd, 0x2f, 0x46, 0x2e, 0x4b, 0x77, 0xff, 0xd2, 0x1a, 0x25, 0x3f, 0x26, 0x10, 0x77, 0xfd, + 0x23, 0x8b, 0xa6, 0xeb, 0x9a, 0xd6, 0x9a, 0x9c, 0x11, 0x16, 0x57, 0xcb, 0x36, 0x87, 0x37, 0xe2, + 0x39, 0x7a, 0x23, 0x05, 0xd4, 0x7f, 0x95, 0x83, 0x8b, 0x8c, 0x13, 0x06, 0x32, 0x49, 0x7c, 0xd8, + 0x81, 0x3a, 0xbc, 0xdd, 0x43, 0x52, 0x42, 0x03, 0x9e, 0x49, 0x3e, 0x4e, 0x8a, 0x11, 0xc6, 0xb8, + 0xf7, 0x90, 0xfd, 0xfe, 0xde, 0xa1, 0xfe, 0x72, 0x01, 0x46, 0x67, 0x6c, 0xcb, 0xd3, 0x9b, 0xde, + 0xc9, 0xba, 0x70, 0x90, 0x83, 0x5f, 0x32, 0x09, 0xfd, 0xb3, 0x6d, 0xdd, 0x6c, 0x89, 0x9d, 0x31, + 0xc6, 0x19, 0xa7, 0x0c, 0xa0, 0x71, 0x38, 0xb9, 0x89, 0xd1, 0xb5, 0x98, 0xa4, 0x03, 0xff, 0xcd, + 0xb1, 0x30, 0x24, 0xb3, 0x84, 0x12, 0x49, 0xc9, 0x39, 0x80, 0x9b, 0x56, 0x72, 0x49, 0xb9, 0xcf, + 0x4e, 0xe6, 0xa5, 0xe3, 0xd1, 0x67, 0xcf, 0x2d, 0xf2, 0x3d, 0xc7, 0x6d, 0xd3, 0x32, 0xc8, 0x05, + 0x38, 0x7b, 0xa7, 0x3e, 0xab, 0x35, 0x6e, 0x57, 0x97, 0x2a, 0x8d, 0x3b, 0x4b, 0xf5, 0xda, 0xec, + 0x4c, 0x75, 0xae, 0x3a, 0x5b, 0x99, 0xe8, 0x23, 0xa7, 0x61, 0x3c, 0x44, 0xcd, 0xdf, 0x59, 0x2c, + 0x2f, 0x4d, 0xe4, 0xc8, 0x29, 0x18, 0x0d, 0x81, 0xd3, 0xcb, 0x2b, 0x13, 0xf9, 0xe7, 0x3e, 0x06, + 0xc3, 0xf8, 0x34, 0x82, 0xfb, 0x45, 0x92, 0x11, 0x18, 0x5c, 0x9e, 0xae, 0xcf, 0x6a, 0x77, 0x91, + 0x09, 0x40, 0xa9, 0x32, 0xbb, 0xc4, 0x18, 0xe6, 0x9e, 0xfb, 0x7f, 0x72, 0x00, 0xf5, 0xb9, 0x95, + 0x9a, 0x20, 0x1c, 0x86, 0x81, 0xea, 0xd2, 0xdd, 0xf2, 0x42, 0x95, 0xd1, 0x0d, 0x42, 0x71, 0xb9, + 0x36, 0xcb, 0x6a, 0x18, 0x82, 0xfe, 0x99, 0x85, 0xe5, 0xfa, 0xec, 0x44, 0x9e, 0x01, 0xb5, 0xd9, + 0x72, 0x65, 0xa2, 0xc0, 0x80, 0xf7, 0xb4, 0xea, 0xca, 0xec, 0x44, 0x91, 0xfd, 0xb9, 0x50, 0x5f, + 0x29, 0xaf, 0x4c, 0xf4, 0xb3, 0x3f, 0xe7, 0xf0, 0xcf, 0x12, 0x63, 0x56, 0x9f, 0x5d, 0xc1, 0x1f, + 0x03, 0xac, 0x09, 0x73, 0xfe, 0xaf, 0x41, 0x86, 0x62, 0xac, 0x2b, 0x55, 0x6d, 0x62, 0x88, 0xfd, + 0x60, 0x2c, 0xd9, 0x0f, 0x60, 0x8d, 0xd3, 0x66, 0x17, 0x97, 0xef, 0xce, 0x4e, 0x0c, 0x33, 0x5e, + 0x8b, 0xb7, 0x19, 0x78, 0x84, 0xfd, 0xa9, 0x2d, 0xb2, 0x3f, 0x47, 0x19, 0x27, 0x6d, 0xb6, 0xbc, + 0x50, 0x2b, 0xaf, 0xcc, 0x4f, 0x8c, 0xb1, 0xf6, 0x20, 0xcf, 0x71, 0x5e, 0x72, 0xa9, 0xbc, 0x38, + 0x3b, 0x31, 0x21, 0x68, 0x2a, 0x0b, 0xd5, 0xa5, 0xdb, 0x13, 0xa7, 0xb0, 0x21, 0xef, 0x2e, 0xe2, + 0x0f, 0xc2, 0x0a, 0xe0, 0x5f, 0xa7, 0x9f, 0xfb, 0x7e, 0x28, 0x2d, 0xd7, 0x71, 0x3b, 0x72, 0x1e, + 0x4e, 0x2f, 0xd7, 0x1b, 0x2b, 0xef, 0xd6, 0x66, 0x63, 0xf2, 0x3e, 0x05, 0xa3, 0x3e, 0x62, 0xa1, + 0xba, 0x74, 0xe7, 0x33, 0x5c, 0xda, 0x3e, 0x68, 0xb1, 0x3c, 0xb3, 0x5c, 0x9f, 0xc8, 0xb3, 0x5e, + 0xf1, 0x41, 0xf7, 0xaa, 0x4b, 0x95, 0xe5, 0x7b, 0xf5, 0x89, 0xc2, 0x73, 0x0f, 0x61, 0x84, 0xe7, + 0x78, 0x5f, 0x76, 0xcc, 0x35, 0xd3, 0x22, 0x4f, 0xc2, 0x85, 0xca, 0xec, 0xdd, 0xea, 0xcc, 0x6c, + 0x63, 0x59, 0xab, 0xde, 0xac, 0x2e, 0xc5, 0x6a, 0x3a, 0x0b, 0xa7, 0xa2, 0xe8, 0x72, 0xad, 0x3a, + 0x91, 0x23, 0xe7, 0x80, 0x44, 0xc1, 0xb7, 0xca, 0x8b, 0x73, 0x13, 0x79, 0xa2, 0xc0, 0x99, 0x28, + 0xbc, 0xba, 0xb4, 0x72, 0x67, 0x69, 0x76, 0xa2, 0xf0, 0xdc, 0x5f, 0xcb, 0xc1, 0xd9, 0xd4, 0x3c, + 0x20, 0x44, 0x85, 0xa7, 0x66, 0x17, 0xca, 0xf5, 0x95, 0xea, 0x4c, 0x7d, 0xb6, 0xac, 0xcd, 0xcc, + 0x37, 0x66, 0xca, 0x2b, 0xb3, 0x37, 0x97, 0xb5, 0x77, 0x1b, 0x37, 0x67, 0x97, 0x66, 0xb5, 0xf2, + 0xc2, 0x44, 0x1f, 0x79, 0x16, 0x26, 0x33, 0x68, 0xea, 0xb3, 0x33, 0x77, 0xb4, 0xea, 0xca, 0xbb, + 0x13, 0x39, 0xf2, 0x0c, 0x3c, 0x99, 0x49, 0xc4, 0x7e, 0x4f, 0xe4, 0xc9, 0x53, 0x70, 0x31, 0x8b, + 0xe4, 0x9d, 0x85, 0x89, 0xc2, 0x73, 0x3f, 0x93, 0x03, 0x92, 0x4c, 0xe4, 0x40, 0x9e, 0x86, 0x4b, + 0x4c, 0x2f, 0x1a, 0xd9, 0x0d, 0x7c, 0x06, 0x9e, 0x4c, 0xa5, 0x90, 0x9a, 0x37, 0x09, 0x4f, 0x64, + 0x90, 0x88, 0xc6, 0x5d, 0x02, 0x25, 0x9d, 0x00, 0x9b, 0xf6, 0xab, 0x39, 0x38, 0x9b, 0xea, 0x8a, + 0x4c, 0xae, 0xc0, 0x47, 0xca, 0x95, 0x45, 0xd6, 0x37, 0x33, 0x2b, 0xd5, 0xe5, 0xa5, 0x7a, 0x63, + 0x71, 0xae, 0xdc, 0x60, 0xda, 0x77, 0xa7, 0x1e, 0xeb, 0xcd, 0xcb, 0xa0, 0xf6, 0xa0, 0x9c, 0x99, + 0x2f, 0x2f, 0xdd, 0x64, 0xc3, 0x8f, 0x7c, 0x04, 0x9e, 0xce, 0xa4, 0x9b, 0x5d, 0x2a, 0x4f, 0x2f, + 0xcc, 0x56, 0x26, 0xf2, 0xe4, 0xa3, 0xf0, 0x4c, 0x26, 0x55, 0xa5, 0x5a, 0xe7, 0x64, 0x85, 0xe7, + 0xf4, 0xc8, 0x64, 0xc4, 0xbe, 0x72, 0x66, 0x79, 0x69, 0xa5, 0x3c, 0xb3, 0x92, 0xa6, 0xd9, 0x17, + 0xe0, 0x6c, 0x04, 0x3b, 0x7d, 0xa7, 0x5e, 0x5d, 0x9a, 0xad, 0xd7, 0x27, 0x72, 0x09, 0x54, 0x20, + 0xda, 0xfc, 0x74, 0xe5, 0xdb, 0xff, 0xd3, 0x53, 0x7d, 0xdf, 0xfe, 0xa3, 0xa7, 0x72, 0x7f, 0xf0, + 0x47, 0x4f, 0xe5, 0xfe, 0xd9, 0x1f, 0x3d, 0x95, 0xfb, 0xec, 0xf5, 0xbd, 0xe4, 0x00, 0xe1, 0xf3, + 0xe2, 0x6a, 0x09, 0x0d, 0xf3, 0x97, 0xfe, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb3, 0x64, 0x30, + 0x8e, 0xf5, 0x8e, 0x01, 0x00, } func (m *Metadata) Marshal() (dAtA []byte, err error) { @@ -31527,6 +31702,52 @@ func (m *OneOf_SFTPSummary) MarshalToSizedBuffer(dAtA []byte) (int, error) { } return len(dAtA) - i, nil } +func (m *OneOf_ContactCreate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *OneOf_ContactCreate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.ContactCreate != nil { + { + size, err := m.ContactCreate.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xc + i-- + dAtA[i] = 0x82 + } + return len(dAtA) - i, nil +} +func (m *OneOf_ContactDelete) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *OneOf_ContactDelete) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.ContactDelete != nil { + { + size, err := m.ContactDelete.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xc + i-- + dAtA[i] = 0x8a + } + return len(dAtA) - i, nil +} func (m *OneOf_WorkloadIdentityCreate) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) @@ -31643,12 +31864,12 @@ func (m *StreamStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n661, err661 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastUploadTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastUploadTime):]) - if err661 != nil { - return 0, err661 + n663, err663 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastUploadTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.LastUploadTime):]) + if err663 != nil { + return 0, err663 } - i -= n661 - i = encodeVarintEvents(dAtA, i, uint64(n661)) + i -= n663 + i = encodeVarintEvents(dAtA, i, uint64(n663)) i-- dAtA[i] = 0x1a if m.LastEventIndex != 0 { @@ -31807,12 +32028,12 @@ func (m *Identity) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0xc2 } } - n665, err665 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.PreviousIdentityExpires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.PreviousIdentityExpires):]) - if err665 != nil { - return 0, err665 + n667, err667 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.PreviousIdentityExpires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.PreviousIdentityExpires):]) + if err667 != nil { + return 0, err667 } - i -= n665 - i = encodeVarintEvents(dAtA, i, uint64(n665)) + i -= n667 + i = encodeVarintEvents(dAtA, i, uint64(n667)) i-- dAtA[i] = 0x1 i-- @@ -31960,12 +32181,12 @@ func (m *Identity) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x4a } - n669, err669 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err669 != nil { - return 0, err669 + n671, err671 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err671 != nil { + return 0, err671 } - i -= n669 - i = encodeVarintEvents(dAtA, i, uint64(n669)) + i -= n671 + i = encodeVarintEvents(dAtA, i, uint64(n671)) i-- dAtA[i] = 0x42 if len(m.KubernetesUsers) > 0 { @@ -38939,6 +39160,184 @@ func (m *UserLoginAccessListInvalid) MarshalToSizedBuffer(dAtA []byte) (int, err return len(dAtA) - i, nil } +func (m *ContactCreate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ContactCreate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ContactCreate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.ContactType != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.ContactType)) + i-- + dAtA[i] = 0x38 + } + if len(m.Email) > 0 { + i -= len(m.Email) + copy(dAtA[i:], m.Email) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Email))) + i-- + dAtA[i] = 0x32 + } + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size, err := m.ConnectionMetadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size, err := m.UserMetadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.ResourceMetadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.Metadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ContactDelete) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ContactDelete) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ContactDelete) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.ContactType != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.ContactType)) + i-- + dAtA[i] = 0x38 + } + if len(m.Email) > 0 { + i -= len(m.Email) + copy(dAtA[i:], m.Email) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Email))) + i-- + dAtA[i] = 0x32 + } + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size, err := m.ConnectionMetadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size, err := m.UserMetadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.ResourceMetadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.Metadata.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -45077,6 +45476,30 @@ func (m *OneOf_SFTPSummary) Size() (n int) { } return n } +func (m *OneOf_ContactCreate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ContactCreate != nil { + l = m.ContactCreate.Size() + n += 2 + l + sovEvents(uint64(l)) + } + return n +} +func (m *OneOf_ContactDelete) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ContactDelete != nil { + l = m.ContactDelete.Size() + n += 2 + l + sovEvents(uint64(l)) + } + return n +} func (m *OneOf_WorkloadIdentityCreate) Size() (n int) { if m == nil { return 0 @@ -47731,6 +48154,64 @@ func (m *UserLoginAccessListInvalid) Size() (n int) { return n } +func (m *ContactCreate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Metadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = m.ResourceMetadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = m.UserMetadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = m.ConnectionMetadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovEvents(uint64(l)) + l = len(m.Email) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + if m.ContactType != 0 { + n += 1 + sovEvents(uint64(m.ContactType)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *ContactDelete) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Metadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = m.ResourceMetadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = m.UserMetadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = m.ConnectionMetadata.Size() + n += 1 + l + sovEvents(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovEvents(uint64(l)) + l = len(m.Email) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + if m.ContactType != 0 { + n += 1 + sovEvents(uint64(m.ContactType)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func sovEvents(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -86395,9 +86876,9 @@ func (m *OneOf) Unmarshal(dAtA []byte) error { } m.Event = &OneOf_SFTPSummary{v} iNdEx = postIndex - case 194: + case 192: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WorkloadIdentityCreate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ContactCreate", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -86424,15 +86905,15 @@ func (m *OneOf) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &WorkloadIdentityCreate{} + v := &ContactCreate{} if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } - m.Event = &OneOf_WorkloadIdentityCreate{v} + m.Event = &OneOf_ContactCreate{v} iNdEx = postIndex - case 195: + case 193: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WorkloadIdentityUpdate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ContactDelete", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -86459,15 +86940,15 @@ func (m *OneOf) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &WorkloadIdentityUpdate{} + v := &ContactDelete{} if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } - m.Event = &OneOf_WorkloadIdentityUpdate{v} + m.Event = &OneOf_ContactDelete{v} iNdEx = postIndex - case 196: + case 194: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WorkloadIdentityDelete", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field WorkloadIdentityCreate", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -86494,15 +86975,15 @@ func (m *OneOf) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &WorkloadIdentityDelete{} + v := &WorkloadIdentityCreate{} if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } - m.Event = &OneOf_WorkloadIdentityDelete{v} + m.Event = &OneOf_WorkloadIdentityCreate{v} iNdEx = postIndex - case 198: + case 195: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UserLoginAccessListInvalid", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field WorkloadIdentityUpdate", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -86529,117 +87010,15 @@ func (m *OneOf) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &UserLoginAccessListInvalid{} + v := &WorkloadIdentityUpdate{} if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } - m.Event = &OneOf_UserLoginAccessListInvalid{v} - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipEvents(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthEvents - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *StreamStatus) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: StreamStatus: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: StreamStatus: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UploadID", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.UploadID = string(dAtA[iNdEx:postIndex]) + m.Event = &OneOf_WorkloadIdentityUpdate{v} iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field LastEventIndex", wireType) - } - m.LastEventIndex = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.LastEventIndex |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: + case 196: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastUploadTime", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field WorkloadIdentityDelete", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -86666,64 +87045,15 @@ func (m *StreamStatus) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.LastUploadTime, dAtA[iNdEx:postIndex]); err != nil { + v := &WorkloadIdentityDelete{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } + m.Event = &OneOf_WorkloadIdentityDelete{v} iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipEvents(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthEvents - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *SessionUpload) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: SessionUpload: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: SessionUpload: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 198: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UserLoginAccessListInvalid", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -86750,15 +87080,68 @@ func (m *SessionUpload) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + v := &UserLoginAccessListInvalid{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } + m.Event = &OneOf_UserLoginAccessListInvalid{v} iNdEx = postIndex - case 2: + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StreamStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StreamStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StreamStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SessionMetadata", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UploadID", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -86768,30 +87151,48 @@ func (m *SessionUpload) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.SessionMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.UploadID = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 5: + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LastEventIndex", wireType) + } + m.LastEventIndex = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LastEventIndex |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SessionURL", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field LastUploadTime", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -86801,23 +87202,24 @@ func (m *SessionUpload) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - m.SessionURL = string(dAtA[iNdEx:postIndex]) + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.LastUploadTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -86841,7 +87243,7 @@ func (m *SessionUpload) Unmarshal(dAtA []byte) error { } return nil } -func (m *Identity) Unmarshal(dAtA []byte) error { +func (m *SessionUpload) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -86864,17 +87266,17 @@ func (m *Identity) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Identity: wiretype end group for non-group") + return fmt.Errorf("proto: SessionUpload: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Identity: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: SessionUpload: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -86884,29 +87286,30 @@ func (m *Identity) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - m.User = string(dAtA[iNdEx:postIndex]) + if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Impersonator", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SessionMetadata", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -86916,27 +87319,28 @@ func (m *Identity) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - m.Impersonator = string(dAtA[iNdEx:postIndex]) + if err := m.SessionMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 3: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Roles", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SessionURL", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -86964,11 +87368,62 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Roles = append(m.Roles, string(dAtA[iNdEx:postIndex])) + m.SessionURL = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Identity) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Identity: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Identity: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Usage", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -86996,11 +87451,11 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Usage = append(m.Usage, string(dAtA[iNdEx:postIndex])) + m.User = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 5: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Logins", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Impersonator", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -87028,11 +87483,11 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Logins = append(m.Logins, string(dAtA[iNdEx:postIndex])) + m.Impersonator = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 6: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field KubernetesGroups", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Roles", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -87060,11 +87515,11 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.KubernetesGroups = append(m.KubernetesGroups, string(dAtA[iNdEx:postIndex])) + m.Roles = append(m.Roles, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - case 7: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field KubernetesUsers", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Usage", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -87092,13 +87547,13 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.KubernetesUsers = append(m.KubernetesUsers, string(dAtA[iNdEx:postIndex])) + m.Usage = append(m.Usage, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - case 8: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Expires", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Logins", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -87108,28 +87563,27 @@ func (m *Identity) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Expires, dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Logins = append(m.Logins, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - case 9: + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RouteToCluster", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field KubernetesGroups", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -87157,11 +87611,11 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.RouteToCluster = string(dAtA[iNdEx:postIndex]) + m.KubernetesGroups = append(m.KubernetesGroups, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - case 10: + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field KubernetesCluster", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field KubernetesUsers", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -87189,11 +87643,11 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.KubernetesCluster = string(dAtA[iNdEx:postIndex]) + m.KubernetesUsers = append(m.KubernetesUsers, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - case 11: + case 8: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Traits", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Expires", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -87220,15 +87674,15 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Traits.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Expires, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 12: + case 9: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RouteToApp", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RouteToCluster", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -87238,31 +87692,27 @@ func (m *Identity) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - if m.RouteToApp == nil { - m.RouteToApp = &RouteToApp{} - } - if err := m.RouteToApp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.RouteToCluster = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 13: + case 10: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TeleportCluster", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field KubernetesCluster", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -87290,11 +87740,11 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.TeleportCluster = string(dAtA[iNdEx:postIndex]) + m.KubernetesCluster = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 14: + case 11: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RouteToDatabase", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Traits", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -87321,18 +87771,15 @@ func (m *Identity) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.RouteToDatabase == nil { - m.RouteToDatabase = &RouteToDatabase{} - } - if err := m.RouteToDatabase.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Traits.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 15: + case 12: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DatabaseNames", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RouteToApp", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -87342,27 +87789,131 @@ func (m *Identity) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - m.DatabaseNames = append(m.DatabaseNames, string(dAtA[iNdEx:postIndex])) + if m.RouteToApp == nil { + m.RouteToApp = &RouteToApp{} + } + if err := m.RouteToApp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 16: + case 13: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DatabaseUsers", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TeleportCluster", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TeleportCluster = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RouteToDatabase", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RouteToDatabase == nil { + m.RouteToDatabase = &RouteToDatabase{} + } + if err := m.RouteToDatabase.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DatabaseNames", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DatabaseNames = append(m.DatabaseNames, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DatabaseUsers", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -107551,46 +108102,529 @@ func (m *WorkloadIdentityUpdate) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ResourceMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UserMetadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.UserMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ResourceMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.UserMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ConnectionMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WorkloadIdentityData", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.WorkloadIdentityData == nil { + m.WorkloadIdentityData = &Struct{} + } + if err := m.WorkloadIdentityData.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WorkloadIdentityDelete) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WorkloadIdentityDelete: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WorkloadIdentityDelete: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ResourceMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.UserMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ConnectionMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AccessListInvalidMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AccessListInvalidMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AccessListInvalidMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AccessListName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AccessListName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.User = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MissingRoles", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MissingRoles = append(m.MissingRoles, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UserLoginAccessListInvalid) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UserLoginAccessListInvalid: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UserLoginAccessListInvalid: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 4: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ConnectionMetadata", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AccessListInvalidMetadata", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -107617,13 +108651,13 @@ func (m *WorkloadIdentityUpdate) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ConnectionMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.AccessListInvalidMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 5: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WorkloadIdentityData", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -107650,10 +108684,7 @@ func (m *WorkloadIdentityUpdate) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.WorkloadIdentityData == nil { - m.WorkloadIdentityData = &Struct{} - } - if err := m.WorkloadIdentityData.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -107679,7 +108710,7 @@ func (m *WorkloadIdentityUpdate) Unmarshal(dAtA []byte) error { } return nil } -func (m *WorkloadIdentityDelete) Unmarshal(dAtA []byte) error { +func (m *ContactCreate) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -107702,10 +108733,10 @@ func (m *WorkloadIdentityDelete) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: WorkloadIdentityDelete: wiretype end group for non-group") + return fmt.Errorf("proto: ContactCreate: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: WorkloadIdentityDelete: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ContactCreate: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -107840,62 +108871,11 @@ func (m *WorkloadIdentityDelete) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipEvents(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthEvents - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AccessListInvalidMetadata) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AccessListInvalidMetadata: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AccessListInvalidMetadata: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AccessListName", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -107905,27 +108885,28 @@ func (m *AccessListInvalidMetadata) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthEvents } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthEvents } if postIndex > l { return io.ErrUnexpectedEOF } - m.AccessListName = string(dAtA[iNdEx:postIndex]) + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 2: + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Email", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -107953,13 +108934,13 @@ func (m *AccessListInvalidMetadata) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.User = string(dAtA[iNdEx:postIndex]) + m.Email = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MissingRoles", wireType) + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ContactType", wireType) } - var stringLen uint64 + m.ContactType = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowEvents @@ -107969,24 +108950,11 @@ func (m *AccessListInvalidMetadata) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.ContactType |= ContactType(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MissingRoles = append(m.MissingRoles, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) @@ -108009,7 +108977,7 @@ func (m *AccessListInvalidMetadata) Unmarshal(dAtA []byte) error { } return nil } -func (m *UserLoginAccessListInvalid) Unmarshal(dAtA []byte) error { +func (m *ContactDelete) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -108032,10 +109000,10 @@ func (m *UserLoginAccessListInvalid) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: UserLoginAccessListInvalid: wiretype end group for non-group") + return fmt.Errorf("proto: ContactDelete: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: UserLoginAccessListInvalid: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ContactDelete: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -108073,7 +109041,7 @@ func (m *UserLoginAccessListInvalid) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AccessListInvalidMetadata", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ResourceMetadata", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -108100,11 +109068,77 @@ func (m *UserLoginAccessListInvalid) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.AccessListInvalidMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ResourceMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.UserMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ConnectionMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) } @@ -108137,6 +109171,57 @@ func (m *UserLoginAccessListInvalid) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Email", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Email = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ContactType", wireType) + } + m.ContactType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ContactType |= ContactType(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/api/types/events/oneof.go b/api/types/events/oneof.go index 8aab26ac1d4bb..01a491cdbd593 100644 --- a/api/types/events/oneof.go +++ b/api/types/events/oneof.go @@ -806,6 +806,14 @@ func ToOneOf(in AuditEvent) (*OneOf, error) { out.Event = &OneOf_WorkloadIdentityDelete{ WorkloadIdentityDelete: e, } + case *ContactCreate: + out.Event = &OneOf_ContactCreate{ + ContactCreate: e, + } + case *ContactDelete: + out.Event = &OneOf_ContactDelete{ + ContactDelete: e, + } default: slog.ErrorContext(context.Background(), "Attempted to convert dynamic event of unknown type into protobuf event.", "event_type", in.GetType()) unknown := &Unknown{} diff --git a/lib/events/api.go b/lib/events/api.go index a546c65d8bcd1..0eb7d639c9619 100644 --- a/lib/events/api.go +++ b/lib/events/api.go @@ -848,6 +848,11 @@ const ( WorkloadIdentityUpdateEvent = "workload_identity.update" // WorkloadIdentityDeleteEvent is emitted when a WorkloadIdentity resource is deleted. WorkloadIdentityDeleteEvent = "workload_identity.delete" + + // ContactCreateEvent is emitted when a Contact resource is created. + ContactCreateEvent = "contact.create" + // ContactDeleteEvent is emitted when a Contact resource is deleted. + ContactDeleteEvent = "contact.delete" ) // Add an entry to eventsMap in lib/events/events_test.go when you add diff --git a/lib/events/codes.go b/lib/events/codes.go index 24c95319a08bb..2a7568ebf42f9 100644 --- a/lib/events/codes.go +++ b/lib/events/codes.go @@ -681,6 +681,11 @@ const ( // WorkloadIdentityDeleteCode is the workload identity delete event code. WorkloadIdentityDeleteCode = "WID003I" + // ContactCreateCode is the auto update version create event code. + ContactCreateCode = "TCTC001I" + // ContactDeleteCode is the auto update version delete event code. + ContactDeleteCode = "TCTC002I" + // UnknownCode is used when an event of unknown type is encountered. UnknownCode = apievents.UnknownCode ) diff --git a/lib/events/dynamic.go b/lib/events/dynamic.go index 2f59ddfb820e7..eba6ff4372ac0 100644 --- a/lib/events/dynamic.go +++ b/lib/events/dynamic.go @@ -472,6 +472,10 @@ func FromEventFields(fields EventFields) (events.AuditEvent, error) { case WorkloadIdentityDeleteEvent: e = &events.WorkloadIdentityDelete{} + case ContactCreateEvent: + e = &events.ContactCreate{} + case ContactDeleteEvent: + e = &events.ContactDelete{} default: slog.ErrorContext(context.Background(), "Attempted to convert dynamic event of unknown type into protobuf event.", "event_type", eventType) unknown := &events.Unknown{} diff --git a/lib/events/events_test.go b/lib/events/events_test.go index edee98e728439..7011bd55af4e4 100644 --- a/lib/events/events_test.go +++ b/lib/events/events_test.go @@ -246,6 +246,8 @@ var eventsMap = map[string]apievents.AuditEvent{ WorkloadIdentityCreateEvent: &apievents.WorkloadIdentityCreate{}, WorkloadIdentityUpdateEvent: &apievents.WorkloadIdentityUpdate{}, WorkloadIdentityDeleteEvent: &apievents.WorkloadIdentityDelete{}, + ContactCreateEvent: &apievents.ContactCreate{}, + ContactDeleteEvent: &apievents.ContactDelete{}, } // TestJSON tests JSON marshal events diff --git a/web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx b/web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx index a65be04e962d6..3db495c91dd2c 100644 --- a/web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx +++ b/web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx @@ -287,6 +287,8 @@ const EventIconMap: Record = { [eventCodes.PLUGIN_CREATE]: Icons.Info, [eventCodes.PLUGIN_UPDATE]: Icons.Info, [eventCodes.PLUGIN_DELETE]: Icons.Info, + [eventCodes.CONTACT_CREATE]: Icons.Info, + [eventCodes.CONTACT_DELETE]: Icons.Info, [eventCodes.UNKNOWN]: Icons.Question, }; diff --git a/web/packages/teleport/src/services/audit/makeEvent.ts b/web/packages/teleport/src/services/audit/makeEvent.ts index 4095ee10fa604..ff1ae2a166e56 100644 --- a/web/packages/teleport/src/services/audit/makeEvent.ts +++ b/web/packages/teleport/src/services/audit/makeEvent.ts @@ -1900,6 +1900,20 @@ export const formatters: Formatters = { return `User [${user}] deleted a plugin [${name}]`; }, }, + [eventCodes.CONTACT_CREATE]: { + type: 'contact.create', + desc: 'Contact Created', + format: ({ user, email, contact_type }) => { + return `User [${user}] created a [${contactTypeStr(contact_type)}] contact [${email}]`; + }, + }, + [eventCodes.CONTACT_DELETE]: { + type: 'contact.delete', + desc: 'Contact Deleted', + format: ({ user, email, contact_type }) => { + return `User [${user}] deleted a [${contactTypeStr(contact_type)}] contact [${email}]`; + }, + }, [eventCodes.UNKNOWN]: { type: 'unknown', desc: 'Unknown Event', @@ -1951,3 +1965,14 @@ function formatMembers(members: { member_name: string }[]) { return `${pluralize(memberNames.length, 'member')} [${memberNamesJoined}]`; } + +function contactTypeStr(type: number): string { + switch (type) { + case 1: + return 'Business'; + case 2: + return 'Security'; + default: + return `Unknown type: ${type}`; + } +} diff --git a/web/packages/teleport/src/services/audit/types.ts b/web/packages/teleport/src/services/audit/types.ts index 066c4116b72c8..c50b27a782df2 100644 --- a/web/packages/teleport/src/services/audit/types.ts +++ b/web/packages/teleport/src/services/audit/types.ts @@ -308,6 +308,8 @@ export const eventCodes = { PLUGIN_CREATE: 'PG001I', PLUGIN_UPDATE: 'PG002I', PLUGIN_DELETE: 'PG003I', + CONTACT_CREATE: 'TCTC001I', + CONTACT_DELETE: 'TCTC002I', } as const; /** @@ -1746,6 +1748,20 @@ export type RawEvents = { server_hostname: string; } >; + [eventCodes.CONTACT_CREATE]: RawEvent< + typeof eventCodes.CONTACT_CREATE, + { + email: string; + contact_type: number; + } + >; + [eventCodes.CONTACT_DELETE]: RawEvent< + typeof eventCodes.CONTACT_DELETE, + { + email: string; + contact_type: number; + } + >; }; /** From 510064934f03eb595c897415661cfab3ba7d5516 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Tue, 24 Dec 2024 15:58:28 -0700 Subject: [PATCH 04/30] Fix web session playback when a duration is not provided (#50459) Prior to Teleport 15, the web UI would download the entire session recording into browser memory before playing it back (crashing the browser tab for long sessions). Starting with Teleport 15, session playback is streamed to the browser and played back as it is received instead of waiting for the entire session to be available. A side effect of this change is that the browser needs to know the length of the session in order to render the progress bar during playback. Since the browser starts playing the session before it has received all of it, we started providing the length of the session via a URL query parameter. Some users have grown accustomed to being able to access session recordings at their original URLs (without the duration query parameter). If you attempt to play recordings from these URLs after upgrading to v15, you'll get an error that the duration is missing. To fix this, the web UI needs to request the duration of the session before it can begin playing it (unless the duration is provided via the URL). There are two ways we could get this information: 1. By querying Teleport's audit log 2. By reading the recording twice: once to get to the end event and compute the duration, and a second time to actually play it back. Since we only have a session ID, an audit log query would be inefficient - we have no idea when the session occurred, so we'd have to search from the beginning of time. (This could be resolved by using a UUIDv7 for session IDs, but Teleport uses UUIDv4 today). For this reason, we elect option 2. This commit creates a new web API endpoint that will fetch a session recording file and scan through it in the same way that streaming is done, but instaed of streaming the data through a websocket it simply reads through to the end to compute the session length. The benefit of this approach is that it will generally be faster than option 1 (unless the session is very long), and it effectively pre-downloads the recording file on the Note: option 2 is not without its drawbacks - since the web UI is making two requests that both read the session recording, the audit log will show two separate session_recording.access events. This isn't ideal but it is good enough to get playback working again for customers who don't access playbacks by clicking the "Play" button in the UI. --- lib/client/api.go | 13 ---- lib/client/api_test.go | 75 ------------------- lib/events/auditlog.go | 1 + lib/web/apiserver.go | 1 + lib/web/tty_playback.go | 41 ++++++++++ web/packages/teleport/src/Player/Player.tsx | 68 ++++++++++++++--- web/packages/teleport/src/config.ts | 35 +++++---- .../teleport/src/services/mfa/types.ts | 3 +- .../src/services/recordings/recordings.ts | 7 ++ 9 files changed, 128 insertions(+), 116 deletions(-) diff --git a/lib/client/api.go b/lib/client/api.go index 88693bc768bb4..6cc0caae15286 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -5032,19 +5032,6 @@ func findActiveApps(keyRing *KeyRing) ([]tlsca.RouteToApp, error) { return apps, nil } -// getDesktopEventWebURL returns the web UI URL users can access to -// watch a desktop session recording in the browser -func getDesktopEventWebURL(proxyHost string, cluster string, sid *session.ID, events []events.EventFields) string { - if len(events) < 1 { - return "" - } - start := events[0].GetTimestamp() - end := events[len(events)-1].GetTimestamp() - duration := end.Sub(start) - - return fmt.Sprintf("https://%s/web/cluster/%s/session/%s?recordingType=desktop&durationMs=%d", proxyHost, cluster, sid, duration/time.Millisecond) -} - // SearchSessionEvents allows searching for session events with a full pagination support. func (tc *TeleportClient) SearchSessionEvents(ctx context.Context, fromUTC, toUTC time.Time, pageSize int, order types.EventOrder, max int) ([]apievents.AuditEvent, error) { ctx, span := tc.Tracer.Start( diff --git a/lib/client/api_test.go b/lib/client/api_test.go index 9da016c7af401..136d338b01e39 100644 --- a/lib/client/api_test.go +++ b/lib/client/api_test.go @@ -27,7 +27,6 @@ import ( "math" "os" "testing" - "time" "github.com/coreos/go-semver/semver" "github.com/google/go-cmp/cmp" @@ -45,10 +44,8 @@ import ( "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/lib/cryptosuites" "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/observability/tracing" - "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/utils" ) @@ -929,78 +926,6 @@ func TestFormatConnectToProxyErr(t *testing.T) { } } -func TestGetDesktopEventWebURL(t *testing.T) { - initDate := time.Date(2021, 1, 1, 12, 0, 0, 0, time.UTC) - - tt := []struct { - name string - proxyHost string - cluster string - sid session.ID - events []events.EventFields - expected string - }{ - { - name: "nil events", - events: nil, - expected: "", - }, - { - name: "empty events", - events: make([]events.EventFields, 0), - expected: "", - }, - { - name: "two events, 1000 ms duration", - proxyHost: "host", - cluster: "cluster", - sid: "session_id", - events: []events.EventFields{ - { - "time": initDate, - }, - { - "time": initDate.Add(1000 * time.Millisecond), - }, - }, - expected: "https://host/web/cluster/cluster/session/session_id?recordingType=desktop&durationMs=1000", - }, - { - name: "multiple events", - proxyHost: "host", - cluster: "cluster", - sid: "session_id", - events: []events.EventFields{ - { - "time": initDate, - }, - { - "time": initDate.Add(10 * time.Millisecond), - }, - { - "time": initDate.Add(20 * time.Millisecond), - }, - { - "time": initDate.Add(30 * time.Millisecond), - }, - { - "time": initDate.Add(40 * time.Millisecond), - }, - { - "time": initDate.Add(50 * time.Millisecond), - }, - }, - expected: "https://host/web/cluster/cluster/session/session_id?recordingType=desktop&durationMs=50", - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expected, getDesktopEventWebURL(tc.proxyHost, tc.cluster, &tc.sid, tc.events)) - }) - } -} - type mockRoleGetter func(ctx context.Context) ([]types.Role, error) func (m mockRoleGetter) GetRoles(ctx context.Context) ([]types.Role, error) { diff --git a/lib/events/auditlog.go b/lib/events/auditlog.go index 51180746cbe7f..3570171f40996 100644 --- a/lib/events/auditlog.go +++ b/lib/events/auditlog.go @@ -555,6 +555,7 @@ func (l *AuditLog) StreamSessionEvents(ctx context.Context, sessionID session.ID } protoReader := NewProtoReader(rawSession) + defer protoReader.Close() for { if ctx.Err() != nil { diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index e293a1dfa6535..9a080398ba71d 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -850,6 +850,7 @@ func (h *Handler) bindDefaultEndpoints() { h.GET("/webapi/sites/:site/events/search", h.WithClusterAuth(h.clusterSearchEvents)) // search site events h.GET("/webapi/sites/:site/events/search/sessions", h.WithClusterAuth(h.clusterSearchSessionEvents)) // search site session events h.GET("/webapi/sites/:site/ttyplayback/:sid", h.WithClusterAuth(h.ttyPlaybackHandle)) + h.GET("/webapi/sites/:site/sessionlength/:sid", h.WithClusterAuth(h.sessionLengthHandle)) // scp file transfer h.GET("/webapi/sites/:site/nodes/:server/:login/scp", h.WithClusterAuth(h.transferFile)) diff --git a/lib/web/tty_playback.go b/lib/web/tty_playback.go index f601f4237666c..76c456a4a7010 100644 --- a/lib/web/tty_playback.go +++ b/lib/web/tty_playback.go @@ -53,6 +53,47 @@ const ( actionPause = byte(1) ) +func (h *Handler) sessionLengthHandle( + w http.ResponseWriter, + r *http.Request, + p httprouter.Params, + sctx *SessionContext, + site reversetunnelclient.RemoteSite, +) (interface{}, error) { + sID := p.ByName("sid") + if sID == "" { + return nil, trace.BadParameter("missing session ID in request URL") + } + + ctx, cancel := context.WithCancel(r.Context()) + defer cancel() + + clt, err := sctx.GetUserClient(ctx, site) + if err != nil { + return nil, trace.Wrap(err) + } + + evts, errs := clt.StreamSessionEvents(ctx, session.ID(sID), 0) + for { + select { + case err := <-errs: + return nil, trace.Wrap(err) + case evt, ok := <-evts: + if !ok { + return nil, trace.NotFound("could not find end event for session %v", sID) + } + switch evt := evt.(type) { + case *events.SessionEnd: + return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + case *events.WindowsDesktopSessionEnd: + return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + case *events.DatabaseSessionEnd: + return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + } + } + } +} + func (h *Handler) ttyPlaybackHandle( w http.ResponseWriter, r *http.Request, diff --git a/web/packages/teleport/src/Player/Player.tsx b/web/packages/teleport/src/Player/Player.tsx index 190f8afb1b558..7d26a785d1830 100644 --- a/web/packages/teleport/src/Player/Player.tsx +++ b/web/packages/teleport/src/Player/Player.tsx @@ -16,20 +16,22 @@ * along with this program. If not, see . */ +import { useCallback, useEffect } from 'react'; import styled from 'styled-components'; -import { Flex, Box } from 'design'; - +import { Flex, Box, Indicator } from 'design'; import { Danger } from 'design/Alert'; -import { useParams, useLocation } from 'teleport/components/Router'; +import { makeSuccessAttempt, useAsync } from 'shared/hooks/useAsync'; +import { useParams, useLocation } from 'teleport/components/Router'; import session from 'teleport/services/websession'; import { UrlPlayerParams } from 'teleport/config'; import { getUrlParameter } from 'teleport/services/history'; - import { RecordingType } from 'teleport/services/recordings'; +import useTeleport from 'teleport/useTeleport'; + import ActionBar from './ActionBar'; import { DesktopPlayer } from './DesktopPlayer'; import SshPlayer from './SshPlayer'; @@ -38,19 +40,44 @@ import Tabs, { TabItem } from './PlayerTabs'; const validRecordingTypes = ['ssh', 'k8s', 'desktop', 'database']; export function Player() { + const ctx = useTeleport(); const { sid, clusterId } = useParams(); const { search } = useLocation(); + useEffect(() => { + document.title = `Play ${sid} • ${clusterId}`; + }, [sid, clusterId]); + const recordingType = getUrlParameter( 'recordingType', search ) as RecordingType; - const durationMs = Number(getUrlParameter('durationMs', search)); + + // In order to render the progress bar, we need to know the length of the session. + // All in-product links to the session player should include the session duration in the URL. + // Some users manually build the URL based on the session ID and don't specify the session duration. + // For those cases, we make a separate API call to get the duration. + const [fetchDurationAttempt, fetchDuration] = useAsync( + useCallback( + () => ctx.recordingsService.fetchRecordingDuration(clusterId, sid), + [ctx.recordingsService, clusterId, sid] + ) + ); const validRecordingType = validRecordingTypes.includes(recordingType); - const validDurationMs = Number.isInteger(durationMs) && durationMs > 0; + const durationMs = Number(getUrlParameter('durationMs', search)); + const shouldFetchSessionDuration = + validRecordingType && (!Number.isInteger(durationMs) || durationMs <= 0); + + useEffect(() => { + if (shouldFetchSessionDuration) { + fetchDuration(); + } + }, [fetchDuration, shouldFetchSessionDuration]); - document.title = `Play ${sid} • ${clusterId}`; + const combinedAttempt = shouldFetchSessionDuration + ? fetchDurationAttempt + : makeSuccessAttempt({ durationMs }); function onLogout() { session.logout(); @@ -69,13 +96,25 @@ export function Player() { ); } - if (!validDurationMs) { + if ( + combinedAttempt.status === '' || + combinedAttempt.status === 'processing' + ) { + return ( + + + + + + ); + } + if (combinedAttempt.status === 'error') { return ( - Invalid query parameter durationMs:{' '} - {getUrlParameter('durationMs', search)}, should be an integer. + Unable to determine the length of this session. The session + recording may be incomplete or corrupted. @@ -101,15 +140,20 @@ export function Player() { ) : ( - + )} ); } + const StyledPlayer = styled.div` display: flex; height: 100%; diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts index 6980b827784cd..c8883b598eebf 100644 --- a/web/packages/teleport/src/config.ts +++ b/web/packages/teleport/src/config.ts @@ -17,21 +17,12 @@ */ import { generatePath } from 'react-router'; -import { mergeDeep } from 'shared/utils/highbar'; -import { IncludedResourceMode } from 'shared/components/UnifiedResources'; -import generateResourcePath from './generateResourcePath'; +import { IncludedResourceMode } from 'shared/components/UnifiedResources'; -import { defaultEntitlements } from './entitlement'; +import { mergeDeep } from 'shared/utils/highbar'; import { - AwsOidcPolicyPreset, - IntegrationKind, - PluginKind, - Regions, -} from './services/integrations'; - -import type { Auth2faType, AuthProvider, AuthType, @@ -39,12 +30,23 @@ import type { PrimaryAuthType, } from 'shared/services'; +import { + AwsOidcPolicyPreset, + IntegrationKind, + PluginKind, + Regions, +} from 'teleport/services/integrations'; +import { KubeResourceKind } from 'teleport/services/kube/types'; + +import { defaultEntitlements } from './entitlement'; + +import generateResourcePath from './generateResourcePath'; + import type { SortType } from 'teleport/services/agents'; import type { RecordingType } from 'teleport/services/recordings'; -import type { WebauthnAssertionResponse } from './services/mfa'; +import type { WebauthnAssertionResponse } from 'teleport/services/mfa'; import type { ParticipantMode } from 'teleport/services/session'; -import type { YamlSupportedResourceKind } from './services/yaml/types'; -import type { KubeResourceKind } from './services/kube/types'; +import type { YamlSupportedResourceKind } from 'teleport/services/yaml/types'; const cfg = { /** @deprecated Use cfg.edition instead. */ @@ -268,6 +270,7 @@ const cfg = { ttyPlaybackWsAddr: 'wss://:fqdn/v1/webapi/sites/:clusterId/ttyplayback/:sid?access_token=:token', // TODO(zmb3): get token out of URL activeAndPendingSessionsPath: '/v1/webapi/sites/:clusterId/sessions', + sessionDurationPath: '/v1/webapi/sites/:clusterId/sessionlength/:sid', kubernetesPath: '/v1/webapi/sites/:clusterId/kubernetes?searchAsRoles=:searchAsRoles?&limit=:limit?&startKey=:startKey?&query=:query?&search=:search?&sort=:sort?', @@ -778,6 +781,10 @@ const cfg = { return generatePath(cfg.api.activeAndPendingSessionsPath, { clusterId }); }, + getSessionDurationUrl(clusterId: string, sid: string) { + return generatePath(cfg.api.sessionDurationPath, { clusterId, sid }); + }, + getUnifiedResourcesUrl(clusterId: string, params: UrlResourcesParams) { return generateResourcePath(cfg.api.unifiedResourcesPath, { clusterId, diff --git a/web/packages/teleport/src/services/mfa/types.ts b/web/packages/teleport/src/services/mfa/types.ts index f1292c50c99cd..382d7831f82fe 100644 --- a/web/packages/teleport/src/services/mfa/types.ts +++ b/web/packages/teleport/src/services/mfa/types.ts @@ -18,8 +18,7 @@ import { AuthProviderType } from 'shared/services'; -import { Base64urlString } from '../auth/types'; -import { CreateNewHardwareDeviceRequest } from '../auth/types'; +import { Base64urlString, CreateNewHardwareDeviceRequest } from '../auth/types'; export type DeviceType = 'totp' | 'webauthn' | 'sso'; diff --git a/web/packages/teleport/src/services/recordings/recordings.ts b/web/packages/teleport/src/services/recordings/recordings.ts index e27ca67beea03..ba71160aa1795 100644 --- a/web/packages/teleport/src/services/recordings/recordings.ts +++ b/web/packages/teleport/src/services/recordings/recordings.ts @@ -45,4 +45,11 @@ export default class RecordingsService { return { recordings: events.map(makeRecording), startKey: json.startKey }; }); } + + fetchRecordingDuration( + clusterId: string, + sessionId: string + ): Promise<{ durationMs: number }> { + return api.get(cfg.getSessionDurationUrl(clusterId, sessionId)); + } } From d9a6cd51a1eb5b270d0c84f2d63bc7168a501a4f Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Wed, 25 Dec 2024 10:47:30 -0300 Subject: [PATCH 05/30] [v17] Update docs to mention PostgreSQL access through WebUI (#50569) * feat(docs): add mention to postgres webui access * Apply suggestions from code review Co-authored-by: Paul Gottschling Co-authored-by: STeve (Xin) Huang * refactor(docs): code review suggestions * refactor(docs): expand limitations section * docs: fix typo --------- Co-authored-by: Paul Gottschling Co-authored-by: STeve (Xin) Huang --- .../connect-dialog-custom.png | Bin 0 -> 37143 bytes .../webui-db-sessions/connect-dialog.png | Bin 0 -> 33302 bytes .../resources-connect-dialog.png | Bin 0 -> 41574 bytes .../webui-db-sessions/resources-list.png | Bin 0 -> 19237 bytes .../webui-db-sessions/session-terminal.png | Bin 0 -> 14387 bytes docs/pages/connect-your-client/web-ui.mdx | 54 ++++++++++++++++++ .../postgres-redshift.mdx | 2 + .../enroll-aws-databases/rds.mdx | 2 + .../redshift-serverless.mdx | 2 + .../azure-postgres-mysql.mdx | 2 + .../postgres-cloudsql.mdx | 2 + .../postgres-self-hosted.mdx | 2 + .../auto-user-provisioning/connect.mdx | 2 + .../database-access/pg-access-webui.mdx | 3 + .../includes/database-access/rds-proxy.mdx | 2 + 15 files changed, 73 insertions(+) create mode 100644 docs/img/connect-your-client/webui-db-sessions/connect-dialog-custom.png create mode 100644 docs/img/connect-your-client/webui-db-sessions/connect-dialog.png create mode 100644 docs/img/connect-your-client/webui-db-sessions/resources-connect-dialog.png create mode 100644 docs/img/connect-your-client/webui-db-sessions/resources-list.png create mode 100644 docs/img/connect-your-client/webui-db-sessions/session-terminal.png create mode 100644 docs/pages/includes/database-access/pg-access-webui.mdx diff --git a/docs/img/connect-your-client/webui-db-sessions/connect-dialog-custom.png b/docs/img/connect-your-client/webui-db-sessions/connect-dialog-custom.png new file mode 100644 index 0000000000000000000000000000000000000000..1ed7f0e494230d25cb5a136f282e18342a330d6e GIT binary patch literal 37143 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≧@uq9>+U31^?bS(2g}qefNlRA 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 literal 0 HcmV?d00001 diff --git a/docs/img/connect-your-client/webui-db-sessions/connect-dialog.png b/docs/img/connect-your-client/webui-db-sessions/connect-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..f1614f0a2826eb70d6edbe9d4c2dde0761852e99 GIT binary patch literal 33302 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 literal 0 HcmV?d00001 diff --git a/docs/img/connect-your-client/webui-db-sessions/resources-connect-dialog.png b/docs/img/connect-your-client/webui-db-sessions/resources-connect-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..51137d8769469020eaa2f8216c6bb77bb6248c33 GIT binary patch literal 41574 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�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 literal 0 HcmV?d00001 diff --git a/docs/img/connect-your-client/webui-db-sessions/resources-list.png b/docs/img/connect-your-client/webui-db-sessions/resources-list.png new file mode 100644 index 0000000000000000000000000000000000000000..447fe1a42a13a92275121f2c38c7927c60e488b7 GIT binary patch literal 19237 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 literal 0 HcmV?d00001 diff --git a/docs/img/connect-your-client/webui-db-sessions/session-terminal.png b/docs/img/connect-your-client/webui-db-sessions/session-terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..c062ec00e2aced4c559b3ec1cc8144a3677de067 GIT binary patch literal 14387 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 ad(jk8ut0oti@yKYoRpZHXo2vncmEGWZs0Hg literal 0 HcmV?d00001 diff --git a/docs/pages/connect-your-client/web-ui.mdx b/docs/pages/connect-your-client/web-ui.mdx index be05318f09d57..9bce88e3eac52 100644 --- a/docs/pages/connect-your-client/web-ui.mdx +++ b/docs/pages/connect-your-client/web-ui.mdx @@ -65,3 +65,57 @@ After you save and exit the editor, `tctl` will update the resource: cluster networking configuration has been updated ``` +## Starting a database session + +Starting from version `17.1`, users can establish database sessions using the +Teleport Web UI. Currently, it is supported in PostgreSQL databases. + +To start a new session, locate your database in the resources list and click +"Connect". + +![Resources list](../../img/connect-your-client/webui-db-sessions/resources-list.png) + +For supported databases, the dialog will present the option to start +the session in your browser. + +![Resources list database connect dialog](../../img/connect-your-client/webui-db-sessions/resources-connect-dialog.png) + +After clicking on the "Connect in the browser" button, a new tab will open with +a form. Teleport will pre-fill this form based on your permissions, but you can +adjust the options as needed. + +![New database session connect dialog](../../img/connect-your-client/webui-db-sessions/connect-dialog.png) + +If your user has wildcard permissions (*), you can type custom values into the +form fields. This allows flexibility in selecting specific databases or +credentials. + +![New database session connect dialog with custom values](../../img/connect-your-client/webui-db-sessions/connect-dialog-custom.png) + +Once you've filled in the session details, click the "Connect" button. Your +session will start, and a terminal interface will appear in the browser. + +The browser-based terminal allows you to execute queries and interact with your +database. Follow the on-screen instructions to see available commands and +limitations. + +![Database session terminal](../../img/connect-your-client/webui-db-sessions/session-terminal.png) + + + While the terminal interface provided in the Teleport Web UI is designed to + resemble popular database CLIs such as `psql`, it is a custom implementation + with some differences and limitations: + - **Feature Set:** Not all features available in `psql` are implemented. + For instance, scripting capabilities, query cancellation, or informational + commands like `\d` or `\dt` are currently unsupported. + - **Error Handling:** Error messages and diagnostics might differ from what + users are accustomed to in `psql`. + + These distinctions are designed to maintain a lightweight and secure interface + directly in your browser. For more complex operations, you may prefer + accessing your database from your terminal using `tsh` and your preferred + tool. + + Future updates may expand functionality or address feedback based on user + needs and supported databases. + diff --git a/docs/pages/enroll-resources/database-access/enroll-aws-databases/postgres-redshift.mdx b/docs/pages/enroll-resources/database-access/enroll-aws-databases/postgres-redshift.mdx index 77e487d14bb3b..87be741c8513d 100644 --- a/docs/pages/enroll-resources/database-access/enroll-aws-databases/postgres-redshift.mdx +++ b/docs/pages/enroll-resources/database-access/enroll-aws-databases/postgres-redshift.mdx @@ -205,6 +205,8 @@ $ tsh db connect --db-user=role/example-iam-role --db-name=dev my-redshift +(!docs/pages/includes/database-access/pg-access-webui.mdx!) + To log out of the database and remove credentials: ```code diff --git a/docs/pages/enroll-resources/database-access/enroll-aws-databases/rds.mdx b/docs/pages/enroll-resources/database-access/enroll-aws-databases/rds.mdx index 4837cb6e7a4e4..a07c4b6e4c73d 100644 --- a/docs/pages/enroll-resources/database-access/enroll-aws-databases/rds.mdx +++ b/docs/pages/enroll-resources/database-access/enroll-aws-databases/rds.mdx @@ -295,6 +295,8 @@ Retrieve credentials for the database and connect to it as the `alice` user: $ tsh db connect --db-user=alice --db-name=postgres rds-example ``` +(!docs/pages/includes/database-access/pg-access-webui.mdx!) + The appropriate database command-line client (`psql`, `mysql`, `mariadb`) should be available in `PATH` in order to be able to connect. diff --git a/docs/pages/enroll-resources/database-access/enroll-aws-databases/redshift-serverless.mdx b/docs/pages/enroll-resources/database-access/enroll-aws-databases/redshift-serverless.mdx index 8de82a3035a91..7b01cb3a2a959 100644 --- a/docs/pages/enroll-resources/database-access/enroll-aws-databases/redshift-serverless.mdx +++ b/docs/pages/enroll-resources/database-access/enroll-aws-databases/redshift-serverless.mdx @@ -235,6 +235,8 @@ Type "help" for help. dev=> ``` +(!docs/pages/includes/database-access/pg-access-webui.mdx!) + To log out of the database and remove credentials: ```code diff --git a/docs/pages/enroll-resources/database-access/enroll-azure-databases/azure-postgres-mysql.mdx b/docs/pages/enroll-resources/database-access/enroll-azure-databases/azure-postgres-mysql.mdx index b639ab2a0673a..e18afa60ca7e9 100644 --- a/docs/pages/enroll-resources/database-access/enroll-azure-databases/azure-postgres-mysql.mdx +++ b/docs/pages/enroll-resources/database-access/enroll-azure-databases/azure-postgres-mysql.mdx @@ -428,6 +428,8 @@ $ tsh db connect --db-user=teleport --db-name=postgres azure-db +(!docs/pages/includes/database-access/pg-access-webui.mdx!) + The appropriate database command-line client (`psql`, `mysql`) should be available in the `PATH` of the machine you're running `tsh db connect` from. diff --git a/docs/pages/enroll-resources/database-access/enroll-google-cloud-databases/postgres-cloudsql.mdx b/docs/pages/enroll-resources/database-access/enroll-google-cloud-databases/postgres-cloudsql.mdx index 15a3d9ece0f22..05f58d2e21788 100644 --- a/docs/pages/enroll-resources/database-access/enroll-google-cloud-databases/postgres-cloudsql.mdx +++ b/docs/pages/enroll-resources/database-access/enroll-google-cloud-databases/postgres-cloudsql.mdx @@ -149,6 +149,8 @@ Retrieve credentials for the "cloudsql" example database and connect to it: $ tsh db connect --db-user=cloudsql-user@.iam --db-name=postgres cloudsql ``` +(!docs/pages/includes/database-access/pg-access-webui.mdx!) + To log out of the database and remove credentials: ```code diff --git a/docs/pages/enroll-resources/database-access/enroll-self-hosted-databases/postgres-self-hosted.mdx b/docs/pages/enroll-resources/database-access/enroll-self-hosted-databases/postgres-self-hosted.mdx index 1cbb3d8a0f427..02acd22cd3fdc 100644 --- a/docs/pages/enroll-resources/database-access/enroll-self-hosted-databases/postgres-self-hosted.mdx +++ b/docs/pages/enroll-resources/database-access/enroll-self-hosted-databases/postgres-self-hosted.mdx @@ -142,6 +142,8 @@ To retrieve credentials for a database and connect to it: $ tsh db connect --db-user=postgres --db-name=postgres example-postgres ``` +(!docs/pages/includes/database-access/pg-access-webui.mdx!) + To log out of the database and remove credentials: ```code diff --git a/docs/pages/includes/database-access/auto-user-provisioning/connect.mdx b/docs/pages/includes/database-access/auto-user-provisioning/connect.mdx index 2d55518cc26ec..891efcc664187 100644 --- a/docs/pages/includes/database-access/auto-user-provisioning/connect.mdx +++ b/docs/pages/includes/database-access/auto-user-provisioning/connect.mdx @@ -2,6 +2,8 @@ Now, log into your Teleport cluster and connect to the database: +(!docs/pages/includes/database-access/pg-access-webui.mdx!) + ```code $ tsh login --proxy=teleport.example.com $ tsh db connect --db-name example diff --git a/docs/pages/includes/database-access/pg-access-webui.mdx b/docs/pages/includes/database-access/pg-access-webui.mdx new file mode 100644 index 0000000000000..5f6f7cddb4b33 --- /dev/null +++ b/docs/pages/includes/database-access/pg-access-webui.mdx @@ -0,0 +1,3 @@ + + Starting from version `17.1`, you can now [access your PostgreSQL databases using the Web UI.](../../connect-your-client/web-ui.mdx#starting-a-database-session) + diff --git a/docs/pages/includes/database-access/rds-proxy.mdx b/docs/pages/includes/database-access/rds-proxy.mdx index 348708e348aa9..dccbc48e02bc9 100644 --- a/docs/pages/includes/database-access/rds-proxy.mdx +++ b/docs/pages/includes/database-access/rds-proxy.mdx @@ -184,6 +184,8 @@ Retrieve credentials for the database and connect to it as the `alice` user: $ tsh db connect --db-user=alice --db-name=dev rds-proxy ``` +(!docs/pages/includes/database-access/pg-access-webui.mdx!) + To log out of the database and remove credentials: ```code From b3bb90964b45a6a1d11c290547f555fdab6861b6 Mon Sep 17 00:00:00 2001 From: rosstimothy <39066650+rosstimothy@users.noreply.github.com> Date: Thu, 26 Dec 2024 06:37:16 -0500 Subject: [PATCH 06/30] Fix: prevent race in slog text handler group handling (#50451) (#50502) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/gravitational/teleport/issues/50450. This updates our custom slog text handler to take heavier inspiration from the slog.TextHandler. A variant of handleState used by the slog.TextHandler has been vendored and modified to produce the same output as our custom logrus formatter. Offloading formatting from the SlogTextHandler directly to handleState prevents the race caused in #50450. Additionally, some quality of life improvements were added by moving some code around to reduce file sizes and better define what belongs in a file. Benchmarks indicate that the changes here don't move the needle much. ``` goos: darwin goarch: arm64 pkg: github.com/gravitational/teleport/lib/utils/log cpu: Apple M2 Pro │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ Formatter/logrus/text-12 8.665µ ± 16% 8.187µ ± 9% ~ (p=0.280 n=10) Formatter/logrus/json-12 8.879µ ± 2% 8.820µ ± 1% ~ (p=0.089 n=10) Formatter/slog/default_text-12 3.936µ ± 3% 3.946µ ± 4% ~ (p=0.839 n=10) Formatter/slog/text-12 3.789µ ± 2% 3.431µ ± 1% -9.45% (p=0.000 n=10) Formatter/slog/default_json-12 3.005µ ± 4% 3.032µ ± 3% ~ (p=0.739 n=10) Formatter/slog/json-12 3.029µ ± 6% 3.022µ ± 1% ~ (p=0.381 n=10) geomean 4.675µ 4.557µ -2.52% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ Formatter/logrus/text-12 5.936Ki ± 0% 5.936Ki ± 0% ~ (p=0.752 n=10) Formatter/logrus/json-12 6.212Ki ± 0% 6.211Ki ± 0% ~ (p=0.752 n=10) Formatter/slog/default_text-12 2.534Ki ± 0% 2.534Ki ± 0% ~ (p=1.000 n=10) ¹ Formatter/slog/text-12 2.144Ki ± 0% 2.167Ki ± 0% +1.09% (p=0.000 n=10) Formatter/slog/default_json-12 2.448Ki ± 0% 2.448Ki ± 0% ~ (p=1.000 n=10) ¹ Formatter/slog/json-12 2.318Ki ± 0% 2.318Ki ± 0% ~ (p=1.000 n=10) ¹ geomean 3.231Ki 3.236Ki +0.18% ¹ all samples are equal │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ Formatter/logrus/text-12 54.00 ± 0% 54.00 ± 0% ~ (p=1.000 n=10) ¹ Formatter/logrus/json-12 76.00 ± 0% 76.00 ± 0% ~ (p=1.000 n=10) ¹ Formatter/slog/default_text-12 41.00 ± 0% 41.00 ± 0% ~ (p=1.000 n=10) ¹ Formatter/slog/text-12 52.00 ± 0% 33.00 ± 0% -36.54% (p=0.000 n=10) Formatter/slog/default_json-12 41.00 ± 0% 41.00 ± 0% ~ (p=1.000 n=10) ¹ Formatter/slog/json-12 42.00 ± 0% 42.00 ± 0% ~ (p=1.000 n=10) ¹ geomean 49.70 46.07 -7.30% ¹ all samples are equal ``` --- lib/utils/log/buffer.go | 37 +- lib/utils/log/formatter_test.go | 17 +- lib/utils/log/handle_state.go | 352 +++++++++++++++ lib/utils/log/logrus_formatter.go | 2 +- lib/utils/log/slog.go | 131 ++++++ lib/utils/log/slog_handler.go | 681 ----------------------------- lib/utils/log/slog_handler_test.go | 12 +- lib/utils/log/slog_json_handler.go | 167 +++++++ lib/utils/log/slog_text_handler.go | 359 +++++++++++++++ 9 files changed, 1034 insertions(+), 724 deletions(-) create mode 100644 lib/utils/log/handle_state.go create mode 100644 lib/utils/log/slog.go delete mode 100644 lib/utils/log/slog_handler.go create mode 100644 lib/utils/log/slog_json_handler.go create mode 100644 lib/utils/log/slog_text_handler.go diff --git a/lib/utils/log/buffer.go b/lib/utils/log/buffer.go index c158808bd545c..d12ac9e11bab0 100644 --- a/lib/utils/log/buffer.go +++ b/lib/utils/log/buffer.go @@ -21,6 +21,14 @@ func newBuffer() *buffer { return bufPool.Get().(*buffer) } +func (b *buffer) Len() int { + return len(*b) +} + +func (b *buffer) SetLen(n int) { + *b = (*b)[:n] +} + func (b *buffer) Free() { // To reduce peak allocation, return only smaller buffers to the pool. const maxBufferSize = 16 << 10 @@ -49,35 +57,6 @@ func (b *buffer) WriteByte(c byte) error { return nil } -func (b *buffer) WritePosInt(i int) { - b.WritePosIntWidth(i, 0) -} - -// WritePosIntWidth writes non-negative integer i to the buffer, padded on the left -// by zeroes to the given width. Use a width of 0 to omit padding. -func (b *buffer) WritePosIntWidth(i, width int) { - // Cheap integer to fixed-width decimal ASCII. - // Copied from log/log.go. - - if i < 0 { - panic("negative int") - } - - // Assemble decimal in reverse order. - var bb [20]byte - bp := len(bb) - 1 - for i >= 10 || width > 1 { - width-- - q := i / 10 - bb[bp] = byte('0' + i - q*10) - bp-- - i = q - } - // i < 10 - bb[bp] = byte('0' + i) - b.Write(bb[bp:]) -} - func (b *buffer) String() string { return string(*b) } diff --git a/lib/utils/log/formatter_test.go b/lib/utils/log/formatter_test.go index 39c717df3425d..e11a9f63620fb 100644 --- a/lib/utils/log/formatter_test.go +++ b/lib/utils/log/formatter_test.go @@ -45,7 +45,7 @@ import ( "github.com/gravitational/teleport" ) -const message = "Adding diagnostic debugging handlers.\t To connect with profiler, use `go tool pprof diag_addr`." +const message = "Adding diagnostic debugging handlers.\t To connect with profiler, use go tool pprof diag_addr." var ( logErr = errors.New("the quick brown fox jumped really high") @@ -76,7 +76,6 @@ func TestOutput(t *testing.T) { loc, err := time.LoadLocation("Africa/Cairo") require.NoError(t, err, "failed getting timezone") clock := clockwork.NewFakeClockAt(time.Now().In(loc)) - formattedNow := clock.Now().UTC().Format(time.RFC3339) t.Run("text", func(t *testing.T) { // fieldsRegex matches all the key value pairs emitted after the message and before the caller. All fields are @@ -88,7 +87,7 @@ func TestOutput(t *testing.T) { // 2) the message // 3) the fields // 4) the caller - outputRegex := regexp.MustCompile("(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z)(\\s+.*)(\".*diag_addr`\\.\")(.*)(\\slog/formatter_test.go:\\d{3})") + outputRegex := regexp.MustCompile(`(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)(\s+.*)(".*diag_addr\.")(.*)(\slog/formatter_test.go:\d{3})`) tests := []struct { name string @@ -149,7 +148,7 @@ func TestOutput(t *testing.T) { EnableColors: true, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { if a.Key == slog.TimeKey { - a.Value = slog.StringValue(formattedNow) + a.Value = slog.TimeValue(clock.Now().UTC()) } return a }, @@ -188,7 +187,7 @@ func TestOutput(t *testing.T) { // Match level, and component: DEBU [TEST] assert.Empty(t, cmp.Diff(logrusMatches[2], slogMatches[2]), "level, and component to be identical") - // Match the log message: "Adding diagnostic debugging handlers.\t To connect with profiler, use `go tool pprof diag_addr`.\n" + // Match the log message: "Adding diagnostic debugging handlers.\t To connect with profiler, use go tool pprof diag_addr.\n" assert.Empty(t, cmp.Diff(logrusMatches[3], slogMatches[3]), "expected output messages to be identical") // The last matches are the caller information assert.Equal(t, fmt.Sprintf(" log/formatter_test.go:%d", logrusTestLogLineNumber), logrusMatches[5]) @@ -461,7 +460,13 @@ func TestConcurrentOutput(t *testing.T) { wg.Add(1) go func(i int) { defer wg.Done() - logger.InfoContext(ctx, "Teleport component entered degraded state", "component", i) + logger.InfoContext(ctx, "Teleport component entered degraded state", + slog.Int("component", i), + slog.Group("group", + slog.String("test", "123"), + slog.String("animal", "llama"), + ), + ) }(i) } wg.Wait() diff --git a/lib/utils/log/handle_state.go b/lib/utils/log/handle_state.go new file mode 100644 index 0000000000000..c60132b28e48e --- /dev/null +++ b/lib/utils/log/handle_state.go @@ -0,0 +1,352 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package log + +import ( + "encoding" + "fmt" + "log/slog" + "reflect" + "strconv" + "sync" + "time" + + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + + "github.com/gravitational/teleport" +) + +// handleState adapted from go/src/log/slog/handler.go +type handleState struct { + h *SlogTextHandler + buf *buffer + freeBuf bool // should buf be freed? + prefix *buffer // for text: key prefix + groups *[]string // pool-allocated slice of active groups, for ReplaceAttr +} + +var groupPool = sync.Pool{New: func() any { + s := make([]string, 0, 10) + return &s +}} + +func (s *handleState) free() { + if s.freeBuf { + s.buf.Free() + } + if gs := s.groups; gs != nil { + *gs = (*gs)[:0] + groupPool.Put(gs) + } + s.prefix.Free() +} + +func (s *handleState) openGroups() { + for _, n := range s.h.groups[s.h.nOpenGroups:] { + s.openGroup(n) + } +} + +// openGroup starts a new group of attributes +// with the given name. +func (s *handleState) openGroup(name string) { + s.prefix.WriteString(name) + s.prefix.WriteByte('.') + + // Collect group names for ReplaceAttr. + if s.groups != nil { + *s.groups = append(*s.groups, name) + } +} + +// closeGroup ends the group with the given name. +func (s *handleState) closeGroup(name string) { + *s.prefix = (*s.prefix)[:len(*s.prefix)-len(name)-1 /* for keyComponentSep */] + + if s.groups != nil { + *s.groups = (*s.groups)[:len(*s.groups)-1] + } +} + +// appendAttrs appends the slice of Attrs. +// It reports whether something was appended. +func (s *handleState) appendAttrs(as []slog.Attr) bool { + nonEmpty := false + for _, a := range as { + if s.appendAttr(a) { + nonEmpty = true + } + } + return nonEmpty +} + +// appendAttr appends the Attr's key and value. +// It handles replacement and checking for an empty key. +// It reports whether something was appended. +func (s *handleState) appendAttr(a slog.Attr) bool { + a.Value = a.Value.Resolve() + if rep := s.h.cfg.ReplaceAttr; rep != nil && a.Value.Kind() != slog.KindGroup { + var gs []string + if s.groups != nil { + gs = *s.groups + } + // a.Value is resolved before calling ReplaceAttr, so the user doesn't have to. + a = rep(gs, a) + // The ReplaceAttr function may return an unresolved Attr. + a.Value = a.Value.Resolve() + } + // Elide empty Attrs. + if a.Equal(slog.Attr{}) { + return false + } + + // Handle nested attributes from within component fields. + if a.Key == teleport.ComponentFields { + nonEmpty := false + switch fields := a.Value.Any().(type) { + case map[string]any: + for k, v := range fields { + if s.appendAttr(slog.Any(k, v)) { + nonEmpty = true + } + } + return nonEmpty + case logrus.Fields: + for k, v := range fields { + if s.appendAttr(slog.Any(k, v)) { + nonEmpty = true + } + } + return nonEmpty + } + } + + // Handle special cases before formatting. + if a.Value.Kind() == slog.KindAny { + switch v := a.Value.Any().(type) { + case *slog.Source: + a.Value = slog.StringValue(fmt.Sprintf(" %s:%d", v.File, v.Line)) + case trace.Error: + a.Value = slog.StringValue("[" + v.DebugReport() + "]") + case error: + a.Value = slog.StringValue(fmt.Sprintf("[%v]", v)) + } + } + + if a.Value.Kind() == slog.KindGroup { + attrs := a.Value.Group() + // Output only non-empty groups. + if len(attrs) > 0 { + // The group may turn out to be empty even though it has attrs (for + // example, ReplaceAttr may delete all the attrs). + // So remember where we are in the buffer, to restore the position + // later if necessary. + pos := s.buf.Len() + // Inline a group with an empty key. + if a.Key != "" { + s.openGroup(a.Key) + } + if !s.appendAttrs(attrs) { + s.buf.SetLen(pos) + return false + } + if a.Key != "" { + s.closeGroup(a.Key) + } + } + + return true + } + + if a.Value.Kind() == slog.KindString && a.Key != slog.LevelKey { + val := a.Value.String() + if needsQuoting(val) { + a.Value = slog.StringValue(strconv.Quote(val)) + } + } + + s.appendKey(a.Key) + + // Write the log key directly to avoid quoting + // color formatting that exists. + if a.Key == slog.LevelKey { + s.buf.WriteString(a.Value.String()) + } else { + s.appendValue(a.Value) + } + + return true +} + +func (s *handleState) appendError(err error) { + s.appendString(fmt.Sprintf("!ERROR:%v", err)) +} + +func (s *handleState) appendKey(key string) { + if s.buf.Len() > 0 { + s.buf.WriteString(" ") + } + + // These keys should not be included in the output to match + // the behavior of the lorgus formatter. + if key == slog.TimeKey || + key == teleport.ComponentKey || + key == slog.LevelKey || + key == CallerField || + key == slog.MessageKey || + key == slog.SourceKey { + return + } + + if s.prefix != nil && len(*s.prefix) > 0 { + // TODO: optimize by avoiding allocation. + s.appendString(string(*s.prefix) + key) + } else { + s.appendString(key) + } + + s.buf.WriteByte(':') +} + +func (s *handleState) appendString(str string) { + if str == "" { + return + } + + if needsQuoting(str) { + *s.buf = strconv.AppendQuote(*s.buf, str) + } else { + s.buf.WriteString(str) + } +} + +func (s *handleState) appendValue(v slog.Value) { + defer func() { + if r := recover(); r != nil { + // If it panics with a nil pointer, the most likely cases are + // an encoding.TextMarshaler or error fails to guard against nil, + // in which case "" seems to be the feasible choice. + // + // Adapted from the code in fmt/print.go. + if v := reflect.ValueOf(v.Any()); v.Kind() == reflect.Pointer && v.IsNil() { + s.appendString("") + return + } + + // Otherwise just print the original panic message. + s.appendString(fmt.Sprintf("!PANIC: %v", r)) + } + }() + + if err := appendTextValue(s, v); err != nil { + s.appendError(err) + } +} + +func (s *handleState) appendTime(t time.Time) { + *s.buf = appendRFC3339Millis(*s.buf, t) +} + +func (s *handleState) appendNonBuiltIns(r slog.Record) { + // preformatted Attrs + if pfa := s.h.preformatted; len(pfa) > 0 { + s.buf.WriteString(" ") + s.buf.Write(pfa) + } + // Attrs in Record -- unlike the built-in ones, they are in groups started + // from WithGroup. + // If the record has no Attrs, don't output any groups. + if r.NumAttrs() > 0 { + s.prefix.WriteString(s.h.groupPrefix) + // The group may turn out to be empty even though it has attrs (for + // example, ReplaceAttr may delete all the attrs). + // So remember where we are in the buffer, to restore the position + // later if necessary. + pos := s.buf.Len() + s.openGroups() + empty := true + r.Attrs(func(a slog.Attr) bool { + // The component is handled by the top level handler. + if a.Key == teleport.ComponentKey { + return true + } + if s.appendAttr(a) { + empty = false + } + return true + }) + if empty { + s.buf.SetLen(pos) + } + } +} + +func byteSlice(a any) ([]byte, bool) { + if bs, ok := a.([]byte); ok { + return bs, true + } + // Like Printf's %s, we allow both the slice type and the byte element type to be named. + t := reflect.TypeOf(a) + if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 { + return reflect.ValueOf(a).Bytes(), true + } + return nil, false +} + +func appendTextValue(s *handleState, v slog.Value) error { + switch v.Kind() { + case slog.KindString: + s.appendString(v.String()) + case slog.KindTime: + s.appendTime(v.Time()) + case slog.KindAny: + if tm, ok := v.Any().(encoding.TextMarshaler); ok { + data, err := tm.MarshalText() + if err != nil { + return err + } + // TODO: avoid the conversion to string. + s.appendString(string(data)) + return nil + } + if bs, ok := byteSlice(v.Any()); ok { + // As of Go 1.19, this only allocates for strings longer than 32 bytes. + s.buf.WriteString(strconv.Quote(string(bs))) + return nil + } + s.appendString(fmt.Sprintf("%+v", v.Any())) + case slog.KindInt64: + *s.buf = strconv.AppendInt(*s.buf, v.Int64(), 10) + case slog.KindUint64: + *s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10) + case slog.KindFloat64: + *s.buf = strconv.AppendFloat(*s.buf, v.Float64(), 'g', -1, 64) + case slog.KindBool: + *s.buf = strconv.AppendBool(*s.buf, v.Bool()) + case slog.KindDuration: + *s.buf = append(*s.buf, v.Duration().String()...) + case slog.KindGroup: + *s.buf = fmt.Append(*s.buf, v.Group()) + case slog.KindLogValuer: + *s.buf = fmt.Append(*s.buf, v.Any()) + default: + panic(fmt.Sprintf("bad kind: %s", v.Kind())) + } + return nil +} + +func appendRFC3339Millis(b []byte, t time.Time) []byte { + // Format according to time.RFC3339Nano since it is highly optimized, + // but truncate it to use millisecond resolution. + // Unfortunately, that format trims trailing 0s, so add 1/10 millisecond + // to guarantee that there are exactly 4 digits after the period. + const prefixLen = len("2006-01-02T15:04:05.000") + n := len(b) + t = t.Truncate(time.Millisecond).Add(time.Millisecond / 10) + b = t.AppendFormat(b, time.RFC3339Nano) + b = append(b[:n+prefixLen], b[n+prefixLen+1:]...) // drop the 4th digit + return b +} diff --git a/lib/utils/log/logrus_formatter.go b/lib/utils/log/logrus_formatter.go index 87b3bff3bdc24..a21d922adf809 100644 --- a/lib/utils/log/logrus_formatter.go +++ b/lib/utils/log/logrus_formatter.go @@ -145,7 +145,7 @@ func (tf *TextFormatter) Format(e *logrus.Entry) ([]byte, error) { // write timestamp first if enabled if tf.timestampEnabled { - writeTimeRFC3339(w.b, e.Time) + *w.b = appendRFC3339Millis(*w.b, e.Time.Round(0)) } for _, field := range tf.ExtraFields { diff --git a/lib/utils/log/slog.go b/lib/utils/log/slog.go new file mode 100644 index 0000000000000..b1b0678ec5487 --- /dev/null +++ b/lib/utils/log/slog.go @@ -0,0 +1,131 @@ +/* + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package log + +import ( + "context" + "fmt" + "log/slog" + "reflect" + "strings" + + oteltrace "go.opentelemetry.io/otel/trace" +) + +const ( + // TraceLevel is the logging level when set to Trace verbosity. + TraceLevel = slog.LevelDebug - 1 + + // TraceLevelText is the text representation of Trace verbosity. + TraceLevelText = "TRACE" +) + +// DiscardHandler is a [slog.Handler] that discards all messages. It +// is more efficient than a [slog.Handler] which outputs to [io.Discard] since +// it performs zero formatting. +// TODO(tross): Use slog.DiscardHandler once upgraded to Go 1.24. +type DiscardHandler struct{} + +func (dh DiscardHandler) Enabled(context.Context, slog.Level) bool { return false } +func (dh DiscardHandler) Handle(context.Context, slog.Record) error { return nil } +func (dh DiscardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return dh } +func (dh DiscardHandler) WithGroup(name string) slog.Handler { return dh } + +func addTracingContextToRecord(ctx context.Context, r *slog.Record) { + const ( + traceID = "trace_id" + spanID = "span_id" + ) + + span := oteltrace.SpanFromContext(ctx) + if span == nil { + return + } + + spanContext := span.SpanContext() + if spanContext.HasTraceID() { + r.AddAttrs(slog.String(traceID, spanContext.TraceID().String())) + } + + if spanContext.HasSpanID() { + r.AddAttrs(slog.String(spanID, spanContext.SpanID().String())) + } +} + +// getCaller retrieves source information from the attribute +// and returns the file and line of the caller. The file is +// truncated from the absolute path to package/filename. +func getCaller(s *slog.Source) (file string, line int) { + count := 0 + idx := strings.LastIndexFunc(s.File, func(r rune) bool { + if r == '/' { + count++ + } + + return count == 2 + }) + file = s.File[idx+1:] + line = s.Line + + return file, line +} + +type stringerAttr struct { + fmt.Stringer +} + +// StringerAttr creates a [slog.LogValuer] that will defer to +// the provided [fmt.Stringer]. All slog attributes are always evaluated, +// even if the log event is discarded due to the configured log level. +// A text [slog.Handler] will try to defer evaluation if the attribute is a +// [fmt.Stringer], however, the JSON [slog.Handler] only defers to [json.Marshaler]. +// This means that to defer evaluation and creation of the string representation, +// the object must implement [fmt.Stringer] and [json.Marshaler], otherwise additional +// and unwanted values may be emitted if the logger is configured to use JSON +// instead of text. This wrapping mechanism allows a value that implements [fmt.Stringer], +// to be guaranteed to be lazily constructed and always output the same +// content regardless of the output format. +func StringerAttr(s fmt.Stringer) slog.LogValuer { + return stringerAttr{Stringer: s} +} + +func (s stringerAttr) LogValue() slog.Value { + if s.Stringer == nil { + return slog.StringValue("") + } + return slog.StringValue(s.Stringer.String()) +} + +type typeAttr struct { + val any +} + +// TypeAttr creates a lazily evaluated log value that presents the pretty type name of a value +// as a string. It is roughly equivalent to the '%T' format option, and should only perform +// reflection in the event that logs are actually being generated. +func TypeAttr(val any) slog.LogValuer { + return typeAttr{val} +} + +func (a typeAttr) LogValue() slog.Value { + if t := reflect.TypeOf(a.val); t != nil { + return slog.StringValue(t.String()) + } + return slog.StringValue("nil") +} diff --git a/lib/utils/log/slog_handler.go b/lib/utils/log/slog_handler.go deleted file mode 100644 index b69b83d2a1adb..0000000000000 --- a/lib/utils/log/slog_handler.go +++ /dev/null @@ -1,681 +0,0 @@ -/* - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package log - -import ( - "context" - "fmt" - "io" - "log/slog" - "reflect" - "runtime" - "slices" - "strconv" - "strings" - "sync" - "time" - - "github.com/gravitational/trace" - "github.com/sirupsen/logrus" - oteltrace "go.opentelemetry.io/otel/trace" - - "github.com/gravitational/teleport" -) - -// TraceLevel is the logging level when set to Trace verbosity. -const TraceLevel = slog.LevelDebug - 1 - -// TraceLevelText is the text representation of Trace verbosity. -const TraceLevelText = "TRACE" - -// SlogTextHandler is a [slog.Handler] that outputs messages in a textual -// manner as configured by the Teleport configuration. -type SlogTextHandler struct { - cfg SlogTextHandlerConfig - // withCaller indicates whether the location the log was emitted from - // should be included in the output message. - withCaller bool - // withTimestamp indicates whether the times that the log was emitted at - // should be included in the output message. - withTimestamp bool - // component is the Teleport subcomponent that emitted the log. - component string - // preformatted data from previous calls to WithGroup and WithAttrs. - preformatted []byte - // groupPrefix is for the text handler only. - // It holds the prefix for groups that were already pre-formatted. - // A group will appear here when a call to WithGroup is followed by - // a call to WithAttrs. - groupPrefix buffer - // groups passed in via WithGroup and WithAttrs. - groups []string - // nOpenGroups the number of groups opened in preformatted. - nOpenGroups int - - // mu protects out - it needs to be a pointer so that all cloned - // SlogTextHandler returned from WithAttrs and WithGroup share the - // same mutex. Otherwise, output may be garbled since each clone - // will use its own copy of the mutex to protect out. See - // https://github.com/golang/go/issues/61321 for more details. - mu *sync.Mutex - out io.Writer -} - -// SlogTextHandlerConfig allow the SlogTextHandler functionality -// to be tweaked. -type SlogTextHandlerConfig struct { - // Level is the minimum record level that will be logged. - Level slog.Leveler - // EnableColors allows the level to be printed in color. - EnableColors bool - // Padding to use for various components. - Padding int - // ConfiguredFields are fields explicitly set by users to be included in - // the output message. If there are any entries configured, they will be honored. - // If empty, the default fields will be populated and included in the output. - ConfiguredFields []string - // ReplaceAttr is called to rewrite each non-group attribute before - // it is logged. - ReplaceAttr func(groups []string, a slog.Attr) slog.Attr -} - -// NewSlogTextHandler creates a SlogTextHandler that writes messages to w. -func NewSlogTextHandler(w io.Writer, cfg SlogTextHandlerConfig) *SlogTextHandler { - if cfg.Padding == 0 { - cfg.Padding = defaultComponentPadding - } - - handler := SlogTextHandler{ - cfg: cfg, - withCaller: len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, CallerField), - withTimestamp: len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, TimestampField), - out: w, - mu: &sync.Mutex{}, - } - - if handler.cfg.ConfiguredFields == nil { - handler.cfg.ConfiguredFields = defaultFormatFields - } - - return &handler -} - -// Enabled returns whether the provided level will be included in output. -func (s *SlogTextHandler) Enabled(ctx context.Context, level slog.Level) bool { - minLevel := slog.LevelInfo - if s.cfg.Level != nil { - minLevel = s.cfg.Level.Level() - } - return level >= minLevel -} - -func (s *SlogTextHandler) appendAttr(buf []byte, a slog.Attr) []byte { - if rep := s.cfg.ReplaceAttr; rep != nil && a.Value.Kind() != slog.KindGroup { - var gs []string - if s.groups != nil { - gs = s.groups - } - // Resolve before calling ReplaceAttr, so the user doesn't have to. - a.Value = a.Value.Resolve() - a = rep(gs, a) - } - - // Resolve the Attr's value before doing anything else. - a.Value = a.Value.Resolve() - // Ignore empty Attrs. - if a.Equal(slog.Attr{}) { - return buf - } - - switch a.Value.Kind() { - case slog.KindString: - value := a.Value.String() - if a.Key == slog.TimeKey { - buf = fmt.Append(buf, value) - break - } - - if a.Key == teleport.ComponentFields { - switch fields := a.Value.Any().(type) { - case map[string]any: - for k, v := range fields { - buf = s.appendAttr(buf, slog.Any(k, v)) - } - case logrus.Fields: - for k, v := range fields { - buf = s.appendAttr(buf, slog.Any(k, v)) - } - } - } - - if needsQuoting(value) { - if a.Key == teleport.ComponentKey || a.Key == slog.LevelKey || a.Key == CallerField || a.Key == slog.MessageKey { - if len(buf) > 0 { - buf = fmt.Append(buf, " ") - } - } else { - if len(buf) > 0 { - buf = fmt.Append(buf, " ") - } - buf = fmt.Appendf(buf, "%s%s:", s.groupPrefix, a.Key) - } - buf = strconv.AppendQuote(buf, value) - break - } - - if a.Key == teleport.ComponentKey || a.Key == slog.LevelKey || a.Key == CallerField || a.Key == slog.MessageKey { - if len(buf) > 0 { - buf = fmt.Append(buf, " ") - } - buf = fmt.Appendf(buf, "%s", a.Value.String()) - break - } - - buf = fmt.Appendf(buf, " %s%s:%s", s.groupPrefix, a.Key, a.Value.String()) - case slog.KindGroup: - attrs := a.Value.Group() - // Ignore empty groups. - if len(attrs) == 0 { - return buf - } - // If the key is non-empty, write it out and indent the rest of the attrs. - // Otherwise, inline the attrs. - if a.Key != "" { - s.groupPrefix = fmt.Append(s.groupPrefix, a.Key) - s.groupPrefix = fmt.Append(s.groupPrefix, ".") - } - for _, ga := range attrs { - buf = s.appendAttr(buf, ga) - } - if a.Key != "" { - s.groupPrefix = s.groupPrefix[:len(s.groupPrefix)-len(a.Key)-1 /* for keyComponentSep */] - if s.groups != nil { - s.groups = (s.groups)[:len(s.groups)-1] - } - } - default: - switch err := a.Value.Any().(type) { - case trace.Error: - buf = fmt.Appendf(buf, " error:[%v]", err.DebugReport()) - case error: - buf = fmt.Appendf(buf, " error:[%v]", a.Value) - default: - buf = fmt.Appendf(buf, " %s:%s", a.Key, a.Value) - } - } - return buf -} - -// writeTimeRFC3339 writes the time in [time.RFC3339Nano] to the buffer. -// This takes half the time of [time.Time.AppendFormat]. Adapted from -// go/src/log/slog/handler.go. -func writeTimeRFC3339(buf *buffer, t time.Time) { - year, month, day := t.Date() - buf.WritePosIntWidth(year, 4) - buf.WriteByte('-') - buf.WritePosIntWidth(int(month), 2) - buf.WriteByte('-') - buf.WritePosIntWidth(day, 2) - buf.WriteByte('T') - hour, min, sec := t.Clock() - buf.WritePosIntWidth(hour, 2) - buf.WriteByte(':') - buf.WritePosIntWidth(min, 2) - buf.WriteByte(':') - buf.WritePosIntWidth(sec, 2) - _, offsetSeconds := t.Zone() - if offsetSeconds == 0 { - buf.WriteByte('Z') - } else { - offsetMinutes := offsetSeconds / 60 - if offsetMinutes < 0 { - buf.WriteByte('-') - offsetMinutes = -offsetMinutes - } else { - buf.WriteByte('+') - } - buf.WritePosIntWidth(offsetMinutes/60, 2) - buf.WriteByte(':') - buf.WritePosIntWidth(offsetMinutes%60, 2) - } -} - -// Handle formats the provided record and writes the output to the -// destination. -func (s *SlogTextHandler) Handle(ctx context.Context, r slog.Record) error { - buf := newBuffer() - defer buf.Free() - - addTracingContextToRecord(ctx, &r) - - if s.withTimestamp && !r.Time.IsZero() { - if s.cfg.ReplaceAttr != nil { - *buf = s.appendAttr(*buf, slog.Time(slog.TimeKey, r.Time)) - } else { - writeTimeRFC3339(buf, r.Time) - } - } - - // Processing fields in this manner allows users to - // configure the level and component position in the output. - // This matches the behavior of the original logrus. All other - // fields location in the output message are static. - for _, field := range s.cfg.ConfiguredFields { - switch field { - case LevelField: - var color int - var level string - switch r.Level { - case TraceLevel: - level = "TRACE" - color = gray - case slog.LevelDebug: - level = "DEBUG" - color = gray - case slog.LevelInfo: - level = "INFO" - color = blue - case slog.LevelWarn: - level = "WARN" - color = yellow - case slog.LevelError: - level = "ERROR" - color = red - case slog.LevelError + 1: - level = "FATAL" - color = red - default: - color = blue - level = r.Level.String() - } - - if !s.cfg.EnableColors { - color = noColor - } - - level = padMax(level, defaultLevelPadding) - if color == noColor { - *buf = s.appendAttr(*buf, slog.String(slog.LevelKey, level)) - } else { - *buf = fmt.Appendf(*buf, " \u001B[%dm%s\u001B[0m", color, level) - } - case ComponentField: - // If a component is provided with the attributes, it should be used instead of - // the component set on the handler. Note that if there are multiple components - // specified in the arguments, the one with the lowest index is used and the others are ignored. - // In the example below, the resulting component in the message output would be "alpaca". - // - // logger := logger.With(teleport.ComponentKey, "fish") - // logger.InfoContext(ctx, "llama llama llama", teleport.ComponentKey, "alpaca", "foo", 123, teleport.ComponentKey, "shark") - component := s.component - r.Attrs(func(attr slog.Attr) bool { - if attr.Key == teleport.ComponentKey { - component = fmt.Sprintf("[%v]", attr.Value) - component = strings.ToUpper(padMax(component, s.cfg.Padding)) - if component[len(component)-1] != ' ' { - component = component[:len(component)-1] + "]" - } - - return false - } - - return true - }) - - *buf = s.appendAttr(*buf, slog.String(teleport.ComponentKey, component)) - default: - if _, ok := knownFormatFields[field]; !ok { - return trace.BadParameter("invalid log format key: %v", field) - } - } - } - - *buf = s.appendAttr(*buf, slog.String(slog.MessageKey, r.Message)) - - // Insert preformatted attributes just after built-in ones. - *buf = append(*buf, s.preformatted...) - if r.NumAttrs() > 0 { - if len(s.groups) > 0 { - for _, n := range s.groups[s.nOpenGroups:] { - s.groupPrefix = fmt.Append(s.groupPrefix, n) - s.groupPrefix = fmt.Append(s.groupPrefix, ".") - } - } - - r.Attrs(func(a slog.Attr) bool { - // Skip adding any component attrs since they are processed above. - if a.Key == teleport.ComponentKey { - return true - } - - *buf = s.appendAttr(*buf, a) - return true - }) - } - - if r.PC != 0 && s.withCaller { - fs := runtime.CallersFrames([]uintptr{r.PC}) - f, _ := fs.Next() - - src := &slog.Source{ - Function: f.Function, - File: f.File, - Line: f.Line, - } - - file, line := getCaller(src) - *buf = fmt.Appendf(*buf, " %s:%d", file, line) - } - - buf.WriteByte('\n') - - s.mu.Lock() - defer s.mu.Unlock() - _, err := s.out.Write(*buf) - return err -} - -// WithAttrs clones the current handler with the provided attributes -// added to any existing attributes. The values are preformatted here -// so that they do not need to be formatted per call to Handle. -func (s *SlogTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - if len(attrs) == 0 { - return s - } - s2 := *s - // Force an append to copy the underlying arrays. - s2.preformatted = slices.Clip(s.preformatted) - s2.groups = slices.Clip(s.groups) - - // Add all groups from WithGroup that haven't already been added to the prefix. - if len(s.groups) > 0 { - for _, n := range s.groups[s.nOpenGroups:] { - s2.groupPrefix = fmt.Append(s2.groupPrefix, n) - s2.groupPrefix = fmt.Append(s2.groupPrefix, ".") - } - } - - // Now all groups have been opened. - s2.nOpenGroups = len(s2.groups) - - component := s.component - - // Pre-format the attributes. - for _, a := range attrs { - switch a.Key { - case teleport.ComponentKey: - component = fmt.Sprintf("[%v]", a.Value.String()) - component = strings.ToUpper(padMax(component, s.cfg.Padding)) - if component[len(component)-1] != ' ' { - component = component[:len(component)-1] + "]" - } - case teleport.ComponentFields: - switch fields := a.Value.Any().(type) { - case map[string]any: - for k, v := range fields { - s2.appendAttr(s2.preformatted, slog.Any(k, v)) - } - case logrus.Fields: - for k, v := range fields { - s2.preformatted = s2.appendAttr(s2.preformatted, slog.Any(k, v)) - } - } - default: - s2.preformatted = s2.appendAttr(s2.preformatted, a) - } - } - - s2.component = component - // Remember how many opened groups are in preformattedAttrs, - // so we don't open them again when we handle a Record. - s2.nOpenGroups = len(s2.groups) - return &s2 -} - -// WithGroup opens a new group. -func (s *SlogTextHandler) WithGroup(name string) slog.Handler { - if name == "" { - return s - } - - s2 := *s - s2.groups = append(s2.groups, name) - return &s2 -} - -// SlogJSONHandlerConfig allow the SlogJSONHandler functionality -// to be tweaked. -type SlogJSONHandlerConfig struct { - // Level is the minimum record level that will be logged. - Level slog.Leveler - // ConfiguredFields are fields explicitly set by users to be included in - // the output message. If there are any entries configured, they will be honored. - // If empty, the default fields will be populated and included in the output. - ConfiguredFields []string - // ReplaceAttr is called to rewrite each non-group attribute before - // it is logged. - ReplaceAttr func(groups []string, a slog.Attr) slog.Attr -} - -// SlogJSONHandler is a [slog.Handler] that outputs messages in a json -// format per the config file. -type SlogJSONHandler struct { - *slog.JSONHandler -} - -// NewSlogJSONHandler creates a SlogJSONHandler that outputs to w. -func NewSlogJSONHandler(w io.Writer, cfg SlogJSONHandlerConfig) *SlogJSONHandler { - withCaller := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, CallerField) - withComponent := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, ComponentField) - withTimestamp := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, TimestampField) - - return &SlogJSONHandler{ - JSONHandler: slog.NewJSONHandler(w, &slog.HandlerOptions{ - AddSource: true, - Level: cfg.Level, - ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { - switch a.Key { - case teleport.ComponentKey: - if !withComponent { - return slog.Attr{} - } - if a.Value.Kind() != slog.KindString { - return a - } - - a.Key = ComponentField - case slog.LevelKey: - // The slog.JSONHandler will inject "level" Attr. - // However, this lib's consumer might add an Attr using the same key ("level") and we end up with two records named "level". - // We must check its type before assuming this was injected by the slog.JSONHandler. - lvl, ok := a.Value.Any().(slog.Level) - if !ok { - return a - } - - var level string - switch lvl { - case TraceLevel: - level = "trace" - case slog.LevelDebug: - level = "debug" - case slog.LevelInfo: - level = "info" - case slog.LevelWarn: - level = "warning" - case slog.LevelError: - level = "error" - case slog.LevelError + 1: - level = "fatal" - default: - level = strings.ToLower(lvl.String()) - } - - a.Value = slog.StringValue(level) - case slog.TimeKey: - if !withTimestamp { - return slog.Attr{} - } - - // The slog.JSONHandler will inject "time" Attr. - // However, this lib's consumer might add an Attr using the same key ("time") and we end up with two records named "time". - // We must check its type before assuming this was injected by the slog.JSONHandler. - if a.Value.Kind() != slog.KindTime { - return a - } - - t := a.Value.Time() - if t.IsZero() { - return a - } - - a.Key = TimestampField - a.Value = slog.StringValue(t.Format(time.RFC3339)) - case slog.MessageKey: - // The slog.JSONHandler will inject "msg" Attr. - // However, this lib's consumer might add an Attr using the same key ("msg") and we end up with two records named "msg". - // We must check its type before assuming this was injected by the slog.JSONHandler. - if a.Value.Kind() != slog.KindString { - return a - } - a.Key = messageField - case slog.SourceKey: - if !withCaller { - return slog.Attr{} - } - - // The slog.JSONHandler will inject "source" Attr when AddSource is true. - // However, this lib's consumer might add an Attr using the same key ("source") and we end up with two records named "source". - // We must check its type before assuming this was injected by the slog.JSONHandler. - s, ok := a.Value.Any().(*slog.Source) - if !ok { - return a - } - - file, line := getCaller(s) - a = slog.String(CallerField, fmt.Sprintf("%s:%d", file, line)) - } - - // Convert [slog.KindAny] values that are backed by an [error] or [fmt.Stringer] - // to strings so that only the message is output instead of a json object. The kind is - // first checked to avoid allocating an interface for the values stored inline - // in [slog.Attr]. - if a.Value.Kind() == slog.KindAny { - if err, ok := a.Value.Any().(error); ok { - a.Value = slog.StringValue(err.Error()) - } - - if stringer, ok := a.Value.Any().(fmt.Stringer); ok { - a.Value = slog.StringValue(stringer.String()) - } - } - - return a - }, - }), - } -} - -const ( - traceID = "trace_id" - spanID = "span_id" -) - -func addTracingContextToRecord(ctx context.Context, r *slog.Record) { - span := oteltrace.SpanFromContext(ctx) - if span == nil { - return - } - - spanContext := span.SpanContext() - if spanContext.HasTraceID() { - r.AddAttrs(slog.String(traceID, spanContext.TraceID().String())) - } - - if spanContext.HasSpanID() { - r.AddAttrs(slog.String(spanID, spanContext.SpanID().String())) - } -} - -func (j *SlogJSONHandler) Handle(ctx context.Context, r slog.Record) error { - addTracingContextToRecord(ctx, &r) - return j.JSONHandler.Handle(ctx, r) -} - -// getCaller retrieves source information from the attribute -// and returns the file and line of the caller. The file is -// truncated from the absolute path to package/filename. -func getCaller(s *slog.Source) (file string, line int) { - count := 0 - idx := strings.LastIndexFunc(s.File, func(r rune) bool { - if r == '/' { - count++ - } - - return count == 2 - }) - file = s.File[idx+1:] - line = s.Line - - return file, line -} - -type stringerAttr struct { - fmt.Stringer -} - -// StringerAttr creates a [slog.LogValuer] that will defer to -// the provided [fmt.Stringer]. All slog attributes are always evaluated, -// even if the log event is discarded due to the configured log level. -// A text [slog.Handler] will try to defer evaluation if the attribute is a -// [fmt.Stringer], however, the JSON [slog.Handler] only defers to [json.Marshaler]. -// This means that to defer evaluation and creation of the string representation, -// the object must implement [fmt.Stringer] and [json.Marshaler], otherwise additional -// and unwanted values may be emitted if the logger is configured to use JSON -// instead of text. This wrapping mechanism allows a value that implements [fmt.Stringer], -// to be guaranteed to be lazily constructed and always output the same -// content regardless of the output format. -func StringerAttr(s fmt.Stringer) slog.LogValuer { - return stringerAttr{Stringer: s} -} - -func (s stringerAttr) LogValue() slog.Value { - if s.Stringer == nil { - return slog.StringValue("") - } - return slog.StringValue(s.Stringer.String()) -} - -type typeAttr struct { - val any -} - -// TypeAttr creates a lazily evaluated log value that presents the pretty type name of a value -// as a string. It is roughly equivalent to the '%T' format option, and should only perform -// reflection in the event that logs are actually being generated. -func TypeAttr(val any) slog.LogValuer { - return typeAttr{val} -} - -func (a typeAttr) LogValue() slog.Value { - if t := reflect.TypeOf(a.val); t != nil { - return slog.StringValue(t.String()) - } - return slog.StringValue("nil") -} diff --git a/lib/utils/log/slog_handler_test.go b/lib/utils/log/slog_handler_test.go index 20876b6c4df1c..a75d57fc66994 100644 --- a/lib/utils/log/slog_handler_test.go +++ b/lib/utils/log/slog_handler_test.go @@ -22,13 +22,11 @@ import ( "bytes" "context" "encoding/json" - "fmt" "log/slog" "regexp" "strings" "testing" "testing/slogtest" - "time" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" @@ -41,16 +39,17 @@ import ( func TestSlogTextHandler(t *testing.T) { t.Parallel() clock := clockwork.NewFakeClock() - now := clock.Now().UTC().Format(time.RFC3339) + now := clock.Now().UTC() // Create a handler that doesn't report the caller and automatically sets // the time to whatever time the fake clock has in UTC time. Since the timestamp // is not important for this test overriding, it allows the regex to be simpler. var buf bytes.Buffer h := NewSlogTextHandler(&buf, SlogTextHandlerConfig{ + ConfiguredFields: []string{LevelField, ComponentField, TimestampField}, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { if a.Key == slog.TimeKey { - a.Value = slog.StringValue(now) + a.Value = slog.TimeValue(now) } return a }, @@ -62,8 +61,7 @@ func TestSlogTextHandler(t *testing.T) { // Group 2: verbosity level of output // Group 3: message contents // Group 4: additional attributes - regex := fmt.Sprintf("^(?:(%s)?)\\s?([A-Z]{4})\\s+(\\w+)(?:\\s(.*))?$", now) - lineRegex := regexp.MustCompile(regex) + lineRegex := regexp.MustCompile(`^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)?\s?([A-Z]{4})\s+(\w+)(?:\s(.*))?$`) results := func() []map[string]any { var ms []map[string]any @@ -75,7 +73,7 @@ func TestSlogTextHandler(t *testing.T) { var m map[string]any matches := lineRegex.FindSubmatch(line) if len(matches) == 0 { - assert.Failf(t, "log output did not match regular expression", "regex: %s output: %s", regex, string(line)) + assert.Failf(t, "log output did not match regular expression", "regex: %s output: %s", lineRegex.String(), string(line)) ms = append(ms, m) continue } diff --git a/lib/utils/log/slog_json_handler.go b/lib/utils/log/slog_json_handler.go new file mode 100644 index 0000000000000..f5c3ec4062b09 --- /dev/null +++ b/lib/utils/log/slog_json_handler.go @@ -0,0 +1,167 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package log + +import ( + "context" + "fmt" + "io" + "log/slog" + "slices" + "strings" + "time" + + "github.com/gravitational/teleport" +) + +// SlogJSONHandlerConfig allows the SlogJSONHandler functionality +// to be tweaked. +type SlogJSONHandlerConfig struct { + // Level is the minimum record level that will be logged. + Level slog.Leveler + // ConfiguredFields are fields explicitly set by users to be included in + // the output message. If there are any entries configured, they will be honored. + // If empty, the default fields will be populated and included in the output. + ConfiguredFields []string + // ReplaceAttr is called to rewrite each non-group attribute before + // it is logged. + ReplaceAttr func(groups []string, a slog.Attr) slog.Attr +} + +// SlogJSONHandler is a [slog.Handler] that outputs messages in a json +// format per the config file. +type SlogJSONHandler struct { + *slog.JSONHandler +} + +// NewSlogJSONHandler creates a SlogJSONHandler that outputs to w. +func NewSlogJSONHandler(w io.Writer, cfg SlogJSONHandlerConfig) *SlogJSONHandler { + withCaller := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, CallerField) + withComponent := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, ComponentField) + withTimestamp := len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, TimestampField) + + return &SlogJSONHandler{ + JSONHandler: slog.NewJSONHandler(w, &slog.HandlerOptions{ + AddSource: true, + Level: cfg.Level, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + switch a.Key { + case teleport.ComponentKey: + if !withComponent { + return slog.Attr{} + } + if a.Value.Kind() != slog.KindString { + return a + } + + a.Key = ComponentField + case slog.LevelKey: + // The slog.JSONHandler will inject "level" Attr. + // However, this lib's consumer might add an Attr using the same key ("level") and we end up with two records named "level". + // We must check its type before assuming this was injected by the slog.JSONHandler. + lvl, ok := a.Value.Any().(slog.Level) + if !ok { + return a + } + + var level string + switch lvl { + case TraceLevel: + level = "trace" + case slog.LevelDebug: + level = "debug" + case slog.LevelInfo: + level = "info" + case slog.LevelWarn: + level = "warning" + case slog.LevelError: + level = "error" + case slog.LevelError + 1: + level = "fatal" + default: + level = strings.ToLower(lvl.String()) + } + + a.Value = slog.StringValue(level) + case slog.TimeKey: + if !withTimestamp { + return slog.Attr{} + } + + // The slog.JSONHandler will inject "time" Attr. + // However, this lib's consumer might add an Attr using the same key ("time") and we end up with two records named "time". + // We must check its type before assuming this was injected by the slog.JSONHandler. + if a.Value.Kind() != slog.KindTime { + return a + } + + t := a.Value.Time() + if t.IsZero() { + return a + } + + a.Key = TimestampField + a.Value = slog.StringValue(t.Format(time.RFC3339)) + case slog.MessageKey: + // The slog.JSONHandler will inject "msg" Attr. + // However, this lib's consumer might add an Attr using the same key ("msg") and we end up with two records named "msg". + // We must check its type before assuming this was injected by the slog.JSONHandler. + if a.Value.Kind() != slog.KindString { + return a + } + a.Key = messageField + case slog.SourceKey: + if !withCaller { + return slog.Attr{} + } + + // The slog.JSONHandler will inject "source" Attr when AddSource is true. + // However, this lib's consumer might add an Attr using the same key ("source") and we end up with two records named "source". + // We must check its type before assuming this was injected by the slog.JSONHandler. + s, ok := a.Value.Any().(*slog.Source) + if !ok { + return a + } + + file, line := getCaller(s) + a = slog.String(CallerField, fmt.Sprintf("%s:%d", file, line)) + } + + // Convert [slog.KindAny] values that are backed by an [error] or [fmt.Stringer] + // to strings so that only the message is output instead of a json object. The kind is + // first checked to avoid allocating an interface for the values stored inline + // in [slog.Attr]. + if a.Value.Kind() == slog.KindAny { + if err, ok := a.Value.Any().(error); ok { + a.Value = slog.StringValue(err.Error()) + } + + if stringer, ok := a.Value.Any().(fmt.Stringer); ok { + a.Value = slog.StringValue(stringer.String()) + } + } + + return a + }, + }), + } +} + +func (j *SlogJSONHandler) Handle(ctx context.Context, r slog.Record) error { + addTracingContextToRecord(ctx, &r) + return j.JSONHandler.Handle(ctx, r) +} diff --git a/lib/utils/log/slog_text_handler.go b/lib/utils/log/slog_text_handler.go new file mode 100644 index 0000000000000..b3bc4900ac64c --- /dev/null +++ b/lib/utils/log/slog_text_handler.go @@ -0,0 +1,359 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package log + +import ( + "context" + "fmt" + "io" + "log/slog" + "runtime" + "slices" + "strings" + "sync" + + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + + "github.com/gravitational/teleport" +) + +// SlogTextHandler is a [slog.Handler] that outputs messages in a textual +// manner as configured by the Teleport configuration. +type SlogTextHandler struct { + cfg SlogTextHandlerConfig + // withCaller indicates whether the location the log was emitted from + // should be included in the output message. + withCaller bool + // withTimestamp indicates whether the times that the log was emitted at + // should be included in the output message. + withTimestamp bool + // component is the Teleport subcomponent that emitted the log. + component string + // preformatted data from previous calls to WithGroup and WithAttrs. + preformatted []byte + // groupPrefix is for the text handler only. + // It holds the prefix for groups that were already pre-formatted. + // A group will appear here when a call to WithGroup is followed by + // a call to WithAttrs. + groupPrefix string + // groups passed in via WithGroup and WithAttrs. + groups []string + // nOpenGroups the number of groups opened in preformatted. + nOpenGroups int + + // mu protects out - it needs to be a pointer so that all cloned + // SlogTextHandler returned from WithAttrs and WithGroup share the + // same mutex. Otherwise, output may be garbled since each clone + // will use its own copy of the mutex to protect out. See + // https://github.com/golang/go/issues/61321 for more details. + mu *sync.Mutex + out io.Writer +} + +// SlogTextHandlerConfig allow the SlogTextHandler functionality +// to be tweaked. +type SlogTextHandlerConfig struct { + // Level is the minimum record level that will be logged. + Level slog.Leveler + // EnableColors allows the level to be printed in color. + EnableColors bool + // Padding to use for various components. + Padding int + // ConfiguredFields are fields explicitly set by users to be included in + // the output message. If there are any entries configured, they will be honored. + // If empty, the default fields will be populated and included in the output. + ConfiguredFields []string + // ReplaceAttr is called to rewrite each non-group attribute before + // it is logged. + ReplaceAttr func(groups []string, a slog.Attr) slog.Attr +} + +// NewSlogTextHandler creates a SlogTextHandler that writes messages to w. +func NewSlogTextHandler(w io.Writer, cfg SlogTextHandlerConfig) *SlogTextHandler { + if cfg.Padding == 0 { + cfg.Padding = defaultComponentPadding + } + + handler := SlogTextHandler{ + cfg: cfg, + withCaller: len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, CallerField), + withTimestamp: len(cfg.ConfiguredFields) == 0 || slices.Contains(cfg.ConfiguredFields, TimestampField), + out: w, + mu: &sync.Mutex{}, + } + + if handler.cfg.ConfiguredFields == nil { + handler.cfg.ConfiguredFields = defaultFormatFields + } + + return &handler +} + +// Enabled returns whether the provided level will be included in output. +func (s *SlogTextHandler) Enabled(ctx context.Context, level slog.Level) bool { + minLevel := slog.LevelInfo + if s.cfg.Level != nil { + minLevel = s.cfg.Level.Level() + } + return level >= minLevel +} + +func (s *SlogTextHandler) newHandleState(buf *buffer, freeBuf bool) handleState { + state := handleState{ + h: s, + buf: buf, + freeBuf: freeBuf, + prefix: newBuffer(), + } + if s.cfg.ReplaceAttr != nil { + state.groups = groupPool.Get().(*[]string) + *state.groups = append(*state.groups, s.groups[:s.nOpenGroups]...) + } + return state +} + +// Handle formats the provided record and writes the output to the +// destination. +func (s *SlogTextHandler) Handle(ctx context.Context, r slog.Record) error { + state := s.newHandleState(newBuffer(), true) + defer state.free() + + addTracingContextToRecord(ctx, &r) + + // Built-in attributes. They are not in a group. + stateGroups := state.groups + state.groups = nil // So ReplaceAttrs sees no groups instead of the pre groups. + rep := s.cfg.ReplaceAttr + + if s.withTimestamp && !r.Time.IsZero() { + if rep == nil { + state.appendKey(slog.TimeKey) + state.appendTime(r.Time.Round(0)) + } else { + state.appendAttr(slog.Time(slog.TimeKey, r.Time.Round(0))) + } + } + + // Processing fields in this manner allows users to + // configure the level and component position in the output. + // This matches the behavior of the original logrus. All other + // fields location in the output message are static. + for _, field := range s.cfg.ConfiguredFields { + switch field { + case LevelField: + var color int + var level string + switch r.Level { + case TraceLevel: + level = "TRACE" + color = gray + case slog.LevelDebug: + level = "DEBUG" + color = gray + case slog.LevelInfo: + level = "INFO" + color = blue + case slog.LevelWarn: + level = "WARN" + color = yellow + case slog.LevelError: + level = "ERROR" + color = red + case slog.LevelError + 1: + level = "FATAL" + color = red + default: + color = blue + level = r.Level.String() + } + + if !s.cfg.EnableColors { + color = noColor + } + + level = padMax(level, defaultLevelPadding) + if color != noColor { + level = fmt.Sprintf("\u001B[%dm%s\u001B[0m", color, level) + } + + if rep == nil { + state.appendKey(slog.LevelKey) + // Write the level directly to stat to avoid quoting + // color formatting that exists. + state.buf.WriteString(level) + } else { + state.appendAttr(slog.String(slog.LevelKey, level)) + } + case ComponentField: + // If a component is provided with the attributes, it should be used instead of + // the component set on the handler. Note that if there are multiple components + // specified in the arguments, the one with the lowest index is used and the others are ignored. + // In the example below, the resulting component in the message output would be "alpaca". + // + // logger := logger.With(teleport.ComponentKey, "fish") + // logger.InfoContext(ctx, "llama llama llama", teleport.ComponentKey, "alpaca", "foo", 123, teleport.ComponentKey, "shark") + component := s.component + r.Attrs(func(attr slog.Attr) bool { + if attr.Key != teleport.ComponentKey { + return true + } + component = fmt.Sprintf("[%v]", attr.Value) + component = strings.ToUpper(padMax(component, s.cfg.Padding)) + if component[len(component)-1] != ' ' { + component = component[:len(component)-1] + "]" + } + + return false + }) + + if rep == nil { + state.appendKey(teleport.ComponentKey) + state.appendString(component) + } else { + state.appendAttr(slog.String(teleport.ComponentKey, component)) + } + default: + if _, ok := knownFormatFields[field]; !ok { + return trace.BadParameter("invalid log format key: %v", field) + } + } + } + + if rep == nil { + state.appendKey(slog.MessageKey) + state.appendString(r.Message) + } else { + state.appendAttr(slog.String(slog.MessageKey, r.Message)) + } + + state.groups = stateGroups // Restore groups passed to ReplaceAttrs. + state.appendNonBuiltIns(r) + + if r.PC != 0 && s.withCaller { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + + src := slog.Source{ + Function: f.Function, + File: f.File, + Line: f.Line, + } + src.File, src.Line = getCaller(&src) + + if rep == nil { + state.appendKey(slog.SourceKey) + state.appendString(fmt.Sprintf("%s:%d", src.File, src.Line)) + } else { + state.appendAttr(slog.Any(slog.SourceKey, &src)) + } + + } + + state.buf.WriteByte('\n') + + s.mu.Lock() + defer s.mu.Unlock() + _, err := s.out.Write(*state.buf) + return err +} + +func (s *SlogTextHandler) clone() *SlogTextHandler { + // We can't use assignment because we can't copy the mutex. + return &SlogTextHandler{ + cfg: s.cfg, + withCaller: s.withCaller, + withTimestamp: s.withTimestamp, + component: s.component, + preformatted: slices.Clip(s.preformatted), + groupPrefix: s.groupPrefix, + groups: slices.Clip(s.groups), + nOpenGroups: s.nOpenGroups, + out: s.out, + mu: s.mu, // mutex shared among all clones of this handler + } +} + +// WithAttrs clones the current handler with the provided attributes +// added to any existing attributes. The values are preformatted here +// so that they do not need to be formatted per call to Handle. +func (s *SlogTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + if len(attrs) == 0 { + return s + } + + s2 := s.clone() + // Pre-format the attributes as an optimization. + state := s2.newHandleState((*buffer)(&s2.preformatted), false) + defer state.free() + state.prefix.WriteString(s.groupPrefix) + + // Remember the position in the buffer, in case all attrs are empty. + pos := state.buf.Len() + state.openGroups() + + nonEmpty := false + for _, a := range attrs { + switch a.Key { + case teleport.ComponentKey: + component := fmt.Sprintf("[%v]", a.Value.String()) + component = strings.ToUpper(padMax(component, s.cfg.Padding)) + if component[len(component)-1] != ' ' { + component = component[:len(component)-1] + "]" + } + s2.component = component + case teleport.ComponentFields: + switch fields := a.Value.Any().(type) { + case map[string]any: + for k, v := range fields { + if state.appendAttr(slog.Any(k, v)) { + nonEmpty = true + } + } + case logrus.Fields: + for k, v := range fields { + if state.appendAttr(slog.Any(k, v)) { + nonEmpty = true + } + } + } + default: + if state.appendAttr(a) { + nonEmpty = true + } + } + } + + if !nonEmpty { + state.buf.SetLen(pos) + } else { + // Remember the new prefix for later keys. + s2.groupPrefix = state.prefix.String() + // Remember how many opened groups are in preformattedAttrs, + // so we don't open them again when we handle a Record. + s2.nOpenGroups = len(s2.groups) + } + + return s2 +} + +// WithGroup opens a new group. +func (s *SlogTextHandler) WithGroup(name string) slog.Handler { + s2 := s.clone() + s2.groups = append(s2.groups, name) + return s2 +} From 24102bd93d4d034b031e7ab28cd26e5399c0bc15 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Thu, 26 Dec 2024 10:38:17 -0700 Subject: [PATCH 07/30] Add Argo CD icon for app access (#50585) Closes #49482 --- .../design/src/ResourceIcon/assets/argocd.svg | 19 +++++++++++++++++++ web/packages/design/src/ResourceIcon/icons.ts | 2 ++ .../src/ResourceIcon/resourceIconSpecs.ts | 1 + 3 files changed, 22 insertions(+) create mode 100644 web/packages/design/src/ResourceIcon/assets/argocd.svg diff --git a/web/packages/design/src/ResourceIcon/assets/argocd.svg b/web/packages/design/src/ResourceIcon/assets/argocd.svg new file mode 100644 index 0000000000000..b419034be124e --- /dev/null +++ b/web/packages/design/src/ResourceIcon/assets/argocd.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/packages/design/src/ResourceIcon/icons.ts b/web/packages/design/src/ResourceIcon/icons.ts index 69656120a82b8..fccf86a27aeff 100644 --- a/web/packages/design/src/ResourceIcon/icons.ts +++ b/web/packages/design/src/ResourceIcon/icons.ts @@ -35,6 +35,7 @@ import apolloIoLight from './assets/apollo.io-light.svg'; import appleDark from './assets/apple-dark.svg'; import appleLight from './assets/apple-light.svg'; import application from './assets/application.svg'; +import argocd from './assets/argocd.svg'; import asana from './assets/asana.svg'; import assembleDark from './assets/assemble-dark.svg'; import assembleLight from './assets/assemble-light.svg'; @@ -322,6 +323,7 @@ export { appleDark, appleLight, application, + argocd, asana, assembleDark, assembleLight, diff --git a/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts b/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts index 17ffe26bbdaeb..67021894b7ad9 100644 --- a/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts +++ b/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts @@ -50,6 +50,7 @@ export const resourceIconSpecs = { 'apollo.io': { dark: i.apolloIoDark, light: i.apolloIoLight }, apple: { dark: i.appleDark, light: i.appleLight }, application: forAllThemes(i.application), + argocd: forAllThemes(i.argocd), asana: forAllThemes(i.asana), assemble: { dark: i.assembleDark, light: i.assembleLight }, atlassian: forAllThemes(i.atlassian), From a06f8e0c495c329ea80e67dc68a2f3c8b22c23a5 Mon Sep 17 00:00:00 2001 From: Gabriel Corado Date: Sat, 28 Dec 2024 13:09:50 -0300 Subject: [PATCH 08/30] Determine session type on stream based on stream watcher events (#50395) (#50592) * refactor: determine session type on stream based on watcher * refactor(auth): early return when is teleport server * refactor: move to function and add tests * chore(lib): fix lint * refactor(events): make private function --- api/types/session_tracker.go | 1 + lib/auth/auth_with_roles.go | 84 ++++++++++---------- lib/auth/auth_with_roles_test.go | 49 +++++++++--- lib/events/auditlog.go | 60 ++++++++++++++ lib/events/auditlog_test.go | 131 +++++++++++++++++++++++++++++++ 5 files changed, 273 insertions(+), 52 deletions(-) diff --git a/api/types/session_tracker.go b/api/types/session_tracker.go index db07ea2578db5..2892db170085c 100644 --- a/api/types/session_tracker.go +++ b/api/types/session_tracker.go @@ -39,6 +39,7 @@ const ( DatabaseSessionKind SessionKind = "db" AppSessionKind SessionKind = "app" WindowsDesktopSessionKind SessionKind = "desktop" + UnknownSessionKind SessionKind = "" ) // SessionParticipantMode is the mode that determines what you can do when you join a session. diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 70cce69fe8260..58c6c89e88fda 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -211,30 +211,14 @@ func (a *ServerWithRoles) actionWithExtendedContext(namespace, kind, verb string // actionForKindSession is a special checker that grants access to session // recordings. It can allow access to a specific recording based on the // `where` section of the user's access rule for kind `session`. -func (a *ServerWithRoles) actionForKindSession(ctx context.Context, namespace string, sid session.ID) (types.SessionKind, error) { - sessionEnd, err := a.findSessionEndEvent(ctx, sid) - - extendContext := func(ctx *services.Context) error { - ctx.Session = sessionEnd +func (a *ServerWithRoles) actionForKindSession(ctx context.Context, namespace string, sid session.ID) error { + extendContext := func(servicesCtx *services.Context) error { + sessionEnd, err := a.findSessionEndEvent(ctx, sid) + servicesCtx.Session = sessionEnd return trace.Wrap(err) } - var sessionKind types.SessionKind - switch e := sessionEnd.(type) { - case *apievents.SessionEnd: - sessionKind = types.SSHSessionKind - if e.KubernetesCluster != "" { - sessionKind = types.KubernetesSessionKind - } - case *apievents.DatabaseSessionEnd: - sessionKind = types.DatabaseSessionKind - case *apievents.AppSessionEnd: - sessionKind = types.AppSessionKind - case *apievents.WindowsDesktopSessionEnd: - sessionKind = types.WindowsDesktopSessionKind - } - - return sessionKind, trace.Wrap(a.actionWithExtendedContext(namespace, types.KindSession, types.VerbRead, extendContext)) + return trace.Wrap(a.actionWithExtendedContext(namespace, types.KindSession, types.VerbRead, extendContext)) } // localServerAction returns an access denied error if the role is not one of the builtin server roles. @@ -6063,29 +6047,25 @@ func (a *ServerWithRoles) ReplaceRemoteLocks(ctx context.Context, clusterName st // channel if one is encountered. Otherwise the event channel is closed when the stream ends. // The event channel is not closed on error to prevent race conditions in downstream select statements. func (a *ServerWithRoles) StreamSessionEvents(ctx context.Context, sessionID session.ID, startIndex int64) (chan apievents.AuditEvent, chan error) { - createErrorChannel := func(err error) (chan apievents.AuditEvent, chan error) { - e := make(chan error, 1) - e <- trace.Wrap(err) - return nil, e - } - err := a.localServerAction() isTeleportServer := err == nil - var sessionType types.SessionKind - if !isTeleportServer { - var err error - sessionType, err = a.actionForKindSession(ctx, apidefaults.Namespace, sessionID) - if err != nil { - c, e := make(chan apievents.AuditEvent), make(chan error, 1) - e <- trace.Wrap(err) - return c, e - } + // StreamSessionEvents can be called internally, and when that + // happens we don't want to emit an event or check for permissions. + if isTeleportServer { + return a.alog.StreamSessionEvents(ctx, sessionID, startIndex) } - // StreamSessionEvents can be called internally, and when that happens we don't want to emit an event. - shouldEmitAuditEvent := !isTeleportServer - if shouldEmitAuditEvent { + if err := a.actionForKindSession(ctx, apidefaults.Namespace, sessionID); err != nil { + c, e := make(chan apievents.AuditEvent), make(chan error, 1) + e <- trace.Wrap(err) + return c, e + } + + // We can only determine the session type after the streaming started. For + // this reason, we delay the emit audit event until the first event or if + // the streaming returns an error. + cb := func(evt apievents.AuditEvent, _ error) { if err := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.SessionRecordingAccess{ Metadata: apievents.Metadata{ Type: events.SessionRecordingAccessEvent, @@ -6093,14 +6073,34 @@ func (a *ServerWithRoles) StreamSessionEvents(ctx context.Context, sessionID ses }, SessionID: sessionID.String(), UserMetadata: a.context.Identity.GetIdentity().GetUserMetadata(), - SessionType: string(sessionType), + SessionType: string(sessionTypeFromStartEvent(evt)), Format: metadata.SessionRecordingFormatFromContext(ctx), }); err != nil { - return createErrorChannel(err) + log.WithError(err).Errorf("Failed to emit stream session event audit event") } } - return a.alog.StreamSessionEvents(ctx, sessionID, startIndex) + return a.alog.StreamSessionEvents(events.ContextWithSessionStartCallback(ctx, cb), sessionID, startIndex) +} + +// sessionTypeFromStartEvent determines the session type given the session start +// event. +func sessionTypeFromStartEvent(sessionStart apievents.AuditEvent) types.SessionKind { + switch e := sessionStart.(type) { + case *apievents.SessionStart: + if e.KubernetesCluster != "" { + return types.KubernetesSessionKind + } + return types.SSHSessionKind + case *apievents.DatabaseSessionStart: + return types.DatabaseSessionKind + case *apievents.AppSessionStart: + return types.AppSessionKind + case *apievents.WindowsDesktopSessionStart: + return types.WindowsDesktopSessionKind + default: + return types.UnknownSessionKind + } } // CreateApp creates a new application resource. diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go index fdf1b644d4de0..c38c6ae1e8ee5 100644 --- a/lib/auth/auth_with_roles_test.go +++ b/lib/auth/auth_with_roles_test.go @@ -2267,7 +2267,29 @@ func TestStreamSessionEvents(t *testing.T) { func TestStreamSessionEvents_SessionType(t *testing.T) { t.Parallel() - srv := newTestTLSServer(t) + authServerConfig := TestAuthServerConfig{ + Dir: t.TempDir(), + Clock: clockwork.NewFakeClockAt(time.Now().Round(time.Second).UTC()), + } + require.NoError(t, authServerConfig.CheckAndSetDefaults()) + + uploader := eventstest.NewMemoryUploader() + localLog, err := events.NewAuditLog(events.AuditLogConfig{ + DataDir: authServerConfig.Dir, + ServerID: authServerConfig.ClusterName, + Clock: authServerConfig.Clock, + UploadHandler: uploader, + }) + require.NoError(t, err) + authServerConfig.AuditLog = localLog + + as, err := NewTestAuthServer(authServerConfig) + require.NoError(t, err) + + srv, err := as.NewTestTLSServer() + require.NoError(t, err) + t.Cleanup(func() { srv.Close() }) + ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -2278,22 +2300,29 @@ func TestStreamSessionEvents_SessionType(t *testing.T) { identity := TestUser(user.GetName()) clt, err := srv.NewClient(identity) require.NoError(t, err) - sessionID := "44c6cea8-362f-11ea-83aa-125400432324" + sessionID := session.NewID() - // Emitting a session end event will cause the listing to correctly locate - // the recording (even if there might not be a recording file to stream). - require.NoError(t, srv.Auth().EmitAuditEvent(ctx, &apievents.DatabaseSessionEnd{ + streamer, err := events.NewProtoStreamer(events.ProtoStreamerConfig{ + Uploader: uploader, + }) + require.NoError(t, err) + stream, err := streamer.CreateAuditStream(ctx, sessionID) + require.NoError(t, err) + // The event is not required to pass through the auth server, we only need + // the upload to be present. + require.NoError(t, stream.RecordEvent(ctx, eventstest.PrepareEvent(&apievents.DatabaseSessionStart{ Metadata: apievents.Metadata{ - Type: events.DatabaseSessionEndEvent, - Code: events.DatabaseSessionEndCode, + Type: events.DatabaseSessionStartEvent, + Code: events.DatabaseSessionStartCode, }, SessionMetadata: apievents.SessionMetadata{ - SessionID: sessionID, + SessionID: sessionID.String(), }, - })) + }))) + require.NoError(t, stream.Complete(ctx)) accessedFormat := teleport.PTY - clt.StreamSessionEvents(metadata.WithSessionRecordingFormatContext(ctx, accessedFormat), session.ID(sessionID), 0) + clt.StreamSessionEvents(metadata.WithSessionRecordingFormatContext(ctx, accessedFormat), sessionID, 0) // Perform the listing an eventually loop to ensure the event is emitted. var searchEvents []apievents.AuditEvent diff --git a/lib/events/auditlog.go b/lib/events/auditlog.go index 3570171f40996..274c3c65c56a6 100644 --- a/lib/events/auditlog.go +++ b/lib/events/auditlog.go @@ -509,9 +509,23 @@ func (l *AuditLog) StreamSessionEvents(ctx context.Context, sessionID session.ID e := make(chan error, 1) c := make(chan apievents.AuditEvent) + sessionStartCh := make(chan apievents.AuditEvent, 1) + if startCb, err := sessionStartCallbackFromContext(ctx); err == nil { + go func() { + evt, ok := <-sessionStartCh + if !ok { + startCb(nil, trace.NotFound("session start event not found")) + return + } + + startCb(evt, nil) + }() + } + rawSession, err := os.CreateTemp(l.playbackDir, string(sessionID)+".stream.tar.*") if err != nil { e <- trace.Wrap(trace.ConvertSystemError(err), "creating temporary stream file") + close(sessionStartCh) return c, e } // The file is still perfectly usable after unlinking it, and the space it's @@ -528,6 +542,7 @@ func (l *AuditLog) StreamSessionEvents(ctx context.Context, sessionID session.ID if err := os.Remove(rawSession.Name()); err != nil { _ = rawSession.Close() e <- trace.Wrap(trace.ConvertSystemError(err), "removing temporary stream file") + close(sessionStartCh) return c, e } @@ -538,6 +553,7 @@ func (l *AuditLog) StreamSessionEvents(ctx context.Context, sessionID session.ID err = trace.NotFound("a recording for session %v was not found", sessionID) } e <- trace.Wrap(err) + close(sessionStartCh) return c, e } l.log.DebugContext(ctx, "Downloaded session to a temporary file for streaming.", @@ -547,6 +563,8 @@ func (l *AuditLog) StreamSessionEvents(ctx context.Context, sessionID session.ID go func() { defer rawSession.Close() + defer close(sessionStartCh) + // this shouldn't be necessary as the position should be already 0 (Download // takes an io.WriterAt), but it's better to be safe than sorry if _, err := rawSession.Seek(0, io.SeekStart); err != nil { @@ -557,6 +575,7 @@ func (l *AuditLog) StreamSessionEvents(ctx context.Context, sessionID session.ID protoReader := NewProtoReader(rawSession) defer protoReader.Close() + firstEvent := true for { if ctx.Err() != nil { e <- trace.Wrap(ctx.Err()) @@ -573,6 +592,11 @@ func (l *AuditLog) StreamSessionEvents(ctx context.Context, sessionID session.ID return } + if firstEvent { + sessionStartCh <- event + firstEvent = false + } + if event.GetIndex() >= startIndex { select { case c <- event: @@ -667,3 +691,39 @@ func (l *AuditLog) periodicSpaceMonitor() { } } } + +// streamSessionEventsContextKey represent context keys used by +// StreamSessionEvents function. +type streamSessionEventsContextKey string + +const ( + // sessionStartCallbackContextKey is the context key used to store the + // session start callback function. + sessionStartCallbackContextKey streamSessionEventsContextKey = "session-start" +) + +// SessionStartCallback is the function used when streaming reaches the start +// event. If any error, such as session not found, the event will be nil, and +// the error will be set. +type SessionStartCallback func(startEvent apievents.AuditEvent, err error) + +// ContextWithSessionStartCallback returns a context.Context containing a +// session start event callback. +func ContextWithSessionStartCallback(ctx context.Context, cb SessionStartCallback) context.Context { + return context.WithValue(ctx, sessionStartCallbackContextKey, cb) +} + +// sessionStartCallbackFromContext returns the session start callback from +// context.Context. +func sessionStartCallbackFromContext(ctx context.Context) (SessionStartCallback, error) { + if ctx == nil { + return nil, trace.BadParameter("context is nil") + } + + cb, ok := ctx.Value(sessionStartCallbackContextKey).(SessionStartCallback) + if !ok { + return nil, trace.BadParameter("session start callback function was not found in the context") + } + + return cb, nil +} diff --git a/lib/events/auditlog_test.go b/lib/events/auditlog_test.go index b76d27a0ee36a..416373e3e6951 100644 --- a/lib/events/auditlog_test.go +++ b/lib/events/auditlog_test.go @@ -154,6 +154,137 @@ func TestConcurrentStreaming(t *testing.T) { } } +func TestStreamSessionEvents(t *testing.T) { + uploader := eventstest.NewMemoryUploader() + alog, err := events.NewAuditLog(events.AuditLogConfig{ + DataDir: t.TempDir(), + Clock: clockwork.NewFakeClock(), + ServerID: "remote", + UploadHandler: uploader, + }) + require.NoError(t, err) + t.Cleanup(func() { alog.Close() }) + + ctx := context.Background() + sid := session.NewID() + sessionEvents := []apievents.AuditEvent{ + &apievents.DatabaseSessionStart{ + Metadata: apievents.Metadata{ + Type: events.DatabaseSessionStartEvent, + Code: events.DatabaseSessionStartCode, + Index: 0, + }, + SessionMetadata: apievents.SessionMetadata{ + SessionID: sid.String(), + }, + }, + &apievents.DatabaseSessionEnd{ + Metadata: apievents.Metadata{ + Type: events.DatabaseSessionEndEvent, + Code: events.DatabaseSessionEndCode, + Index: 1, + }, + SessionMetadata: apievents.SessionMetadata{ + SessionID: sid.String(), + }, + }, + } + + streamer, err := events.NewProtoStreamer(events.ProtoStreamerConfig{ + Uploader: uploader, + }) + require.NoError(t, err) + stream, err := streamer.CreateAuditStream(ctx, sid) + require.NoError(t, err) + for _, event := range sessionEvents { + require.NoError(t, stream.RecordEvent(ctx, eventstest.PrepareEvent(event))) + } + require.NoError(t, stream.Complete(ctx)) + + type callbackResult struct { + event apievents.AuditEvent + err error + } + + t.Run("Success", func(t *testing.T) { + for name, withCallback := range map[string]bool{ + "WithCallback": true, + "WithoutCallback": false, + } { + t.Run(name, func(t *testing.T) { + streamCtx, cancel := context.WithCancel(ctx) + defer cancel() + + callbackCh := make(chan callbackResult, 1) + if withCallback { + streamCtx = events.ContextWithSessionStartCallback(streamCtx, func(ae apievents.AuditEvent, err error) { + callbackCh <- callbackResult{ae, err} + }) + } + + ch, _ := alog.StreamSessionEvents(streamCtx, sid, 0) + for _, event := range sessionEvents { + select { + case receivedEvent := <-ch: + require.NotNil(t, receivedEvent) + require.Equal(t, event.GetCode(), receivedEvent.GetCode()) + require.Equal(t, event.GetType(), receivedEvent.GetType()) + case <-time.After(10 * time.Second): + require.Fail(t, "expected to receive session event %q but got nothing", event.GetType()) + } + } + + if withCallback { + select { + case res := <-callbackCh: + require.NoError(t, res.err) + require.Equal(t, sessionEvents[0].GetCode(), res.event.GetCode()) + require.Equal(t, sessionEvents[0].GetType(), res.event.GetType()) + case <-time.After(10 * time.Second): + require.Fail(t, "expected to receive callback result but got nothing") + } + } + }) + } + }) + + t.Run("Error", func(t *testing.T) { + for name, withCallback := range map[string]bool{ + "WithCallback": true, + "WithoutCallback": false, + } { + t.Run(name, func(t *testing.T) { + streamCtx, cancel := context.WithCancel(ctx) + defer cancel() + + callbackCh := make(chan callbackResult, 1) + if withCallback { + streamCtx = events.ContextWithSessionStartCallback(streamCtx, func(ae apievents.AuditEvent, err error) { + callbackCh <- callbackResult{ae, err} + }) + } + + _, errCh := alog.StreamSessionEvents(streamCtx, session.ID("random"), 0) + select { + case err := <-errCh: + require.Error(t, err) + case <-time.After(10 * time.Second): + require.Fail(t, "expected to get error while stream but got nothing") + } + + if withCallback { + select { + case res := <-callbackCh: + require.Error(t, res.err) + case <-time.After(10 * time.Second): + require.Fail(t, "expected to receive callback result but got nothing") + } + } + }) + } + }) +} + func TestExternalLog(t *testing.T) { m := &eventstest.MockAuditLog{ Emitter: &eventstest.MockRecorderEmitter{}, From f4891b5f621726d75a5ab461d782dddc855135ea Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 30 Dec 2024 10:01:33 +0000 Subject: [PATCH 09/30] [v17] Add support for WorkloadIdentity resource to the Teleport Terraform Provider (#50511) * First pass at trying to generate terraform provider * Wire up types * Fix generation of docs * Add godoc comments * Add tests * Tflint and add example --- api/client/client.go | 46 + .../data-sources/data-sources.mdx | 1 + .../data-sources/workload_identity.mdx | 69 + .../resources/resources.mdx | 1 + .../resources/workload_identity.mdx | 94 ++ integrations/terraform/Makefile | 8 + .../teleport_workload_identity/resource.tf | 22 + integrations/terraform/gen/main.go | 27 + ...protoc-gen-terraform-workloadidentity.yaml | 68 + .../data_source_teleport_workload_identity.go | 82 ++ integrations/terraform/provider/provider.go | 2 + .../resource_teleport_workload_identity.go | 317 +++++ .../fixtures/workload_identity_0_create.tf | 21 + .../fixtures/workload_identity_1_update.tf | 21 + .../testlib/workload_identity_test.go | 143 ++ .../workloadidentity/v1/custom_types.go | 25 + .../workloadidentity/v1/resource_terraform.go | 1177 +++++++++++++++++ 17 files changed, 2124 insertions(+) create mode 100644 docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx create mode 100644 docs/pages/reference/terraform-provider/resources/workload_identity.mdx create mode 100644 integrations/terraform/examples/resources/teleport_workload_identity/resource.tf create mode 100644 integrations/terraform/protoc-gen-terraform-workloadidentity.yaml create mode 100755 integrations/terraform/provider/data_source_teleport_workload_identity.go create mode 100755 integrations/terraform/provider/resource_teleport_workload_identity.go create mode 100644 integrations/terraform/testlib/fixtures/workload_identity_0_create.tf create mode 100644 integrations/terraform/testlib/fixtures/workload_identity_1_update.tf create mode 100644 integrations/terraform/testlib/workload_identity_test.go create mode 100644 integrations/terraform/tfschema/workloadidentity/v1/custom_types.go create mode 100644 integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go diff --git a/api/client/client.go b/api/client/client.go index 10aa123b166a2..bb06da5e4655d 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -5081,6 +5081,52 @@ func (c *Client) UpsertUserLastSeenNotification(ctx context.Context, req *notifi return rsp, trace.Wrap(err) } +// GetWorkloadIdentity returns a workload identity by name. +func (c *Client) GetWorkloadIdentity(ctx context.Context, name string) (*workloadidentityv1pb.WorkloadIdentity, error) { + resp, err := c.WorkloadIdentityResourceServiceClient().GetWorkloadIdentity(ctx, &workloadidentityv1pb.GetWorkloadIdentityRequest{ + Name: name, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// DeleteWorkloadIdentity deletes a workload identity by name. It will throw an +// error if the workload identity does not exist. +func (c *Client) DeleteWorkloadIdentity(ctx context.Context, name string) error { + _, err := c.WorkloadIdentityResourceServiceClient().DeleteWorkloadIdentity(ctx, &workloadidentityv1pb.DeleteWorkloadIdentityRequest{ + Name: name, + }) + if err != nil { + return trace.Wrap(err) + } + return nil +} + +// CreateWorkloadIdentity creates a new workload identity, it will not overwrite +// an existing workload identity with the same name. +func (c *Client) CreateWorkloadIdentity(ctx context.Context, r *workloadidentityv1pb.WorkloadIdentity) (*workloadidentityv1pb.WorkloadIdentity, error) { + resp, err := c.WorkloadIdentityResourceServiceClient().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.CreateWorkloadIdentityRequest{ + WorkloadIdentity: r, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// UpsertWorkloadIdentity creates or updates a workload identity. +func (c *Client) UpsertWorkloadIdentity(ctx context.Context, r *workloadidentityv1pb.WorkloadIdentity) (*workloadidentityv1pb.WorkloadIdentity, error) { + resp, err := c.WorkloadIdentityResourceServiceClient().UpsertWorkloadIdentity(ctx, &workloadidentityv1pb.UpsertWorkloadIdentityRequest{ + WorkloadIdentity: r, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + // ResourceUsageClient returns an unadorned Resource Usage service client, // using the underlying Auth gRPC connection. // Clients connecting to non-Enterprise clusters, or older Teleport versions, diff --git a/docs/pages/reference/terraform-provider/data-sources/data-sources.mdx b/docs/pages/reference/terraform-provider/data-sources/data-sources.mdx index 92e37199d2dd6..0e6dfabb1b4d4 100644 --- a/docs/pages/reference/terraform-provider/data-sources/data-sources.mdx +++ b/docs/pages/reference/terraform-provider/data-sources/data-sources.mdx @@ -34,3 +34,4 @@ The Teleport Terraform provider supports the following data-sources: - [`teleport_trusted_cluster`](./trusted_cluster.mdx) - [`teleport_trusted_device`](./trusted_device.mdx) - [`teleport_user`](./user.mdx) + - [`teleport_workload_identity`](./workload_identity.mdx) diff --git a/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx b/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx new file mode 100644 index 0000000000000..2298d5363d77c --- /dev/null +++ b/docs/pages/reference/terraform-provider/data-sources/workload_identity.mdx @@ -0,0 +1,69 @@ +--- +title: Reference for the teleport_workload_identity Terraform data-source +sidebar_label: workload_identity +description: This page describes the supported values of the teleport_workload_identity data-source of the Teleport Terraform provider. +--- + +{/*Auto-generated file. Do not edit.*/} +{/*To regenerate, navigate to integrations/terraform and run `make docs`.*/} + + + + + +{/* schema generated by tfplugindocs */} +## Schema + +### Optional + +- `metadata` (Attributes) Common metadata that all resources share. (see [below for nested schema](#nested-schema-for-metadata)) +- `spec` (Attributes) The configured properties of the WorkloadIdentity (see [below for nested schema](#nested-schema-for-spec)) +- `sub_kind` (String) Differentiates variations of the same kind. All resources should contain one, even if it is never populated. +- `version` (String) The version of the resource being represented. + +### Nested Schema for `metadata` + +Optional: + +- `description` (String) description is object description. +- `expires` (String) expires is a global expiry time header can be set on any resource in the system. +- `labels` (Map of String) labels is a set of labels. +- `name` (String) name is an object name. + + +### Nested Schema for `spec` + +Optional: + +- `rules` (Attributes) The rules which are evaluated before the WorkloadIdentity can be issued. (see [below for nested schema](#nested-schema-for-specrules)) +- `spiffe` (Attributes) Configuration pertaining to the issuance of SPIFFE-compatible workload identity credentials. (see [below for nested schema](#nested-schema-for-specspiffe)) + +### Nested Schema for `spec.rules` + +Optional: + +- `allow` (Attributes List) A list of rules used to determine if a WorkloadIdentity can be issued. If none are provided, it will be considered a pass. If any are provided, then at least one must pass for the rules to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallow)) + +### Nested Schema for `spec.rules.allow` + +Optional: + +- `conditions` (Attributes List) The conditions that must be met for this rule to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallowconditions)) + +### Nested Schema for `spec.rules.allow.conditions` + +Optional: + +- `attribute` (String) The name of the attribute to evaluate the condition against. +- `equals` (String) An exact string that the attribute must match. + + + + +### Nested Schema for `spec.spiffe` + +Optional: + +- `hint` (String) A freeform text field which is provided to workloads along with a credential produced by this WorkloadIdentity. This can be used to provide additional context that can be used to select between multiple credentials. +- `id` (String) The path of the SPIFFE ID that will be issued to the workload. This should be prefixed with a forward-slash ("/"). This field supports templating using attributes. + diff --git a/docs/pages/reference/terraform-provider/resources/resources.mdx b/docs/pages/reference/terraform-provider/resources/resources.mdx index 51d7bb8d2e3b3..e962f85c38abb 100644 --- a/docs/pages/reference/terraform-provider/resources/resources.mdx +++ b/docs/pages/reference/terraform-provider/resources/resources.mdx @@ -36,3 +36,4 @@ The Teleport Terraform provider supports the following resources: - [`teleport_trusted_cluster`](./trusted_cluster.mdx) - [`teleport_trusted_device`](./trusted_device.mdx) - [`teleport_user`](./user.mdx) + - [`teleport_workload_identity`](./workload_identity.mdx) diff --git a/docs/pages/reference/terraform-provider/resources/workload_identity.mdx b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx new file mode 100644 index 0000000000000..a9d3da4bc7a73 --- /dev/null +++ b/docs/pages/reference/terraform-provider/resources/workload_identity.mdx @@ -0,0 +1,94 @@ +--- +title: Reference for the teleport_workload_identity Terraform resource +sidebar_label: workload_identity +description: This page describes the supported values of the teleport_workload_identity resource of the Teleport Terraform provider. +--- + +{/*Auto-generated file. Do not edit.*/} +{/*To regenerate, navigate to integrations/terraform and run `make docs`.*/} + + + +## Example Usage + +```hcl +resource "teleport_workload_identity" "example" { + version = "v1" + metadata = { + name = "example" + } + spec = { + rules = { + allow = [ + { + conditions = [{ + attribute = "user.name" + equals = "noah" + }] + } + ] + } + spiffe = { + id = "/my/spiffe/id/path" + hint = "my-hint" + } + } +} +``` + +{/* schema generated by tfplugindocs */} +## Schema + +### Optional + +- `metadata` (Attributes) Common metadata that all resources share. (see [below for nested schema](#nested-schema-for-metadata)) +- `spec` (Attributes) The configured properties of the WorkloadIdentity (see [below for nested schema](#nested-schema-for-spec)) +- `sub_kind` (String) Differentiates variations of the same kind. All resources should contain one, even if it is never populated. +- `version` (String) The version of the resource being represented. + +### Nested Schema for `metadata` + +Optional: + +- `description` (String) description is object description. +- `expires` (String) expires is a global expiry time header can be set on any resource in the system. +- `labels` (Map of String) labels is a set of labels. +- `name` (String) name is an object name. + + +### Nested Schema for `spec` + +Optional: + +- `rules` (Attributes) The rules which are evaluated before the WorkloadIdentity can be issued. (see [below for nested schema](#nested-schema-for-specrules)) +- `spiffe` (Attributes) Configuration pertaining to the issuance of SPIFFE-compatible workload identity credentials. (see [below for nested schema](#nested-schema-for-specspiffe)) + +### Nested Schema for `spec.rules` + +Optional: + +- `allow` (Attributes List) A list of rules used to determine if a WorkloadIdentity can be issued. If none are provided, it will be considered a pass. If any are provided, then at least one must pass for the rules to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallow)) + +### Nested Schema for `spec.rules.allow` + +Optional: + +- `conditions` (Attributes List) The conditions that must be met for this rule to be considered passed. (see [below for nested schema](#nested-schema-for-specrulesallowconditions)) + +### Nested Schema for `spec.rules.allow.conditions` + +Optional: + +- `attribute` (String) The name of the attribute to evaluate the condition against. +- `equals` (String) An exact string that the attribute must match. + + + + +### Nested Schema for `spec.spiffe` + +Optional: + +- `hint` (String) A freeform text field which is provided to workloads along with a credential produced by this WorkloadIdentity. This can be used to provide additional context that can be used to select between multiple credentials. +- `id` (String) The path of the SPIFFE ID that will be issued to the workload. This should be prefixed with a forward-slash ("/"). This field supports templating using attributes. + diff --git a/integrations/terraform/Makefile b/integrations/terraform/Makefile index f14246132fd55..68ec5fe4150d5 100644 --- a/integrations/terraform/Makefile +++ b/integrations/terraform/Makefile @@ -115,10 +115,18 @@ endif --terraform_out=config=protoc-gen-terraform-statichostuser.yaml:./tfschema \ teleport/userprovisioning/v2/statichostuser.proto + @protoc \ + -I=../../api/proto \ + -I=$(PROTOBUF_MOD_PATH) \ + --plugin=$(PROTOC_GEN_TERRAFORM) \ + --terraform_out=config=protoc-gen-terraform-workloadidentity.yaml:./tfschema \ + teleport/workloadidentity/v1/resource.proto + mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1/loginrule_terraform.go ./tfschema/loginrule/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1/accesslist_terraform.go ./tfschema/accesslist/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1/access_monitoring_rules_terraform.go ./tfschema/accessmonitoringrules/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2/statichostuser_terraform.go ./tfschema/userprovisioning/v2/ + mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1/resource_terraform.go ./tfschema/workloadidentity/v1/ mv ./tfschema/github.com/gravitational/teleport/api/types/device_terraform.go ./tfschema/devicetrust/v1/ rm -r ./tfschema/github.com/ @go run ./gen/main.go diff --git a/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf b/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf new file mode 100644 index 0000000000000..e48ab1e5d0dd2 --- /dev/null +++ b/integrations/terraform/examples/resources/teleport_workload_identity/resource.tf @@ -0,0 +1,22 @@ +resource "teleport_workload_identity" "example" { + version = "v1" + metadata = { + name = "example" + } + spec = { + rules = { + allow = [ + { + conditions = [{ + attribute = "user.name" + equals = "noah" + }] + } + ] + } + spiffe = { + id = "/my/spiffe/id/path" + hint = "my-hint" + } + } +} \ No newline at end of file diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index 3053e7b56098a..ca639ac758ac2 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -519,6 +519,31 @@ var ( ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/api/types\""}, ForceSetKind: "apitypes.KindStaticHostUser", } + + workloadIdentity = payload{ + Name: "WorkloadIdentity", + TypeName: "WorkloadIdentity", + VarName: "workloadIdentity", + GetMethod: "GetWorkloadIdentity", + CreateMethod: "CreateWorkloadIdentity", + UpsertMethodArity: 2, + UpdateMethod: "UpsertWorkloadIdentity", + DeleteMethod: "DeleteWorkloadIdentity", + ID: "workloadIdentity.Metadata.Name", + Kind: "workload_identity", + HasStaticID: false, + ProtoPackage: "workloadidentityv1", + ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1", + SchemaPackage: "schemav1", + SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/workloadidentity/v1", + TerraformResourceType: "teleport_workload_identity", + // Since [RFD 153](https://github.com/gravitational/teleport/blob/master/rfd/0153-resource-guidelines.md) + // resources are plain structs + IsPlainStruct: true, + // As 153-style resources don't have CheckAndSetDefaults, we must set the Kind manually. + // We import the package containing kinds, then use ForceSetKind. + ForceSetKind: `"workload_identity"`, + } ) func main() { @@ -570,6 +595,8 @@ func genTFSchema() { generateDataSource(accessMonitoringRule, pluralDataSource) generateResource(staticHostUser, pluralResource) generateDataSource(staticHostUser, pluralDataSource) + generateResource(workloadIdentity, pluralResource) + generateDataSource(workloadIdentity, pluralDataSource) } func generateResource(p payload, tpl string) { diff --git a/integrations/terraform/protoc-gen-terraform-workloadidentity.yaml b/integrations/terraform/protoc-gen-terraform-workloadidentity.yaml new file mode 100644 index 0000000000000..016f3209037ba --- /dev/null +++ b/integrations/terraform/protoc-gen-terraform-workloadidentity.yaml @@ -0,0 +1,68 @@ +--- +target_package_name: "v1" +default_package_name: "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" +duration_custom_type: Duration +use_state_for_unknown_by_default: true + +# Top-level type names to export +types: + - "WorkloadIdentity" + +# These import paths were not being automatically picked up by +# protoc-gen-terraform without these overrides +import_path_overrides: + "types": "github.com/gravitational/teleport/api/types" + "wrappers": "github.com/gravitational/teleport/api/types/wrappers" + "durationpb": "google.golang.org/protobuf/types/known/durationpb" + "timestamppb": "google.golang.org/protobuf/types/known/timestamppb" + "structpb": "google.golang.org/protobuf/types/known/structpb" + "v1": "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "v11": "github.com/gravitational/teleport/api/gen/proto/go/teleport/label/v1" + "github_com_gravitational_teleport_integrations_terraform_tfschema": "github.com/gravitational/teleport/integrations/terraform/tfschema" + + +# id field is required for integration tests. It is not used by provider. +# We have to add it manually (might be removed in the future versions). +injected_fields: + WorkloadIdentity: + - name: id + type: github.com/hashicorp/terraform-plugin-framework/types.StringType + computed: true + plan_modifiers: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" + +# These fields will be excluded +exclude_fields: + # Metadata (we id resources by name on our side) + - "WorkloadIdentity.metadata.id" + +# These fields will be marked as Computed: true +computed_fields: + # Metadata + - "WorkloadIdentity.metadata.namespace" + - "WorkloadIdentity.kind" + +# These fields will be marked as Required: true +required_fields: [] + + +plan_modifiers: + # Force to recreate resource if it's name changes + Metadata.name: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + +# This must be defined for the generator to be happy, but in reality all time +# fields are overridden (because the protobuf timestamps contain locks and the +# linter gets mad if we use raw structs instead of pointers). +time_type: + type: "PlaceholderType" +duration_type: + type: "PlaceholderType" + +validators: + # Expires must be in the future + Metadata.expires: + - github_com_gravitational_teleport_integrations_terraform_tfschema.MustTimeBeInFuture() + +custom_types: + "WorkloadIdentity.metadata.expires": Timestamp \ No newline at end of file diff --git a/integrations/terraform/provider/data_source_teleport_workload_identity.go b/integrations/terraform/provider/data_source_teleport_workload_identity.go new file mode 100755 index 0000000000000..1b1d15fb99dcd --- /dev/null +++ b/integrations/terraform/provider/data_source_teleport_workload_identity.go @@ -0,0 +1,82 @@ +// Code generated by _gen/main.go DO NOT EDIT +/* +Copyright 2015-2024 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "context" + + + "github.com/gravitational/trace" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/workloadidentity/v1" +) + +// dataSourceTeleportWorkloadIdentityType is the data source metadata type +type dataSourceTeleportWorkloadIdentityType struct{} + +// dataSourceTeleportWorkloadIdentity is the resource +type dataSourceTeleportWorkloadIdentity struct { + p Provider +} + +// GetSchema returns the data source schema +func (r dataSourceTeleportWorkloadIdentityType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return schemav1.GenSchemaWorkloadIdentity(ctx) +} + +// NewDataSource creates the empty data source +func (r dataSourceTeleportWorkloadIdentityType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { + return dataSourceTeleportWorkloadIdentity{ + p: *(p.(*Provider)), + }, nil +} + +// Read reads teleport WorkloadIdentity +func (r dataSourceTeleportWorkloadIdentity) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { + var id types.String + diags := req.Config.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + workloadIdentityI, err := r.p.Client.GetWorkloadIdentity(ctx, id.Value) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + var state types.Object + workloadIdentity := workloadIdentityI + + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentity, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index ec2648df1b274..13b20d20c434f 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -504,6 +504,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_installer": resourceTeleportInstallerType{}, "teleport_access_monitoring_rule": resourceTeleportAccessMonitoringRuleType{}, "teleport_static_host_user": resourceTeleportStaticHostUserType{}, + "teleport_workload_identity": resourceTeleportWorkloadIdentityType{}, }, nil } @@ -531,6 +532,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc "teleport_installer": dataSourceTeleportInstallerType{}, "teleport_access_monitoring_rule": dataSourceTeleportAccessMonitoringRuleType{}, "teleport_static_host_user": dataSourceTeleportStaticHostUserType{}, + "teleport_workload_identity": dataSourceTeleportWorkloadIdentityType{}, }, nil } diff --git a/integrations/terraform/provider/resource_teleport_workload_identity.go b/integrations/terraform/provider/resource_teleport_workload_identity.go new file mode 100755 index 0000000000000..e5c59e0993b44 --- /dev/null +++ b/integrations/terraform/provider/resource_teleport_workload_identity.go @@ -0,0 +1,317 @@ +// Code generated by _gen/main.go DO NOT EDIT +/* +Copyright 2015-2024 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "context" + "fmt" + + workloadidentityv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + + "github.com/gravitational/teleport/integrations/lib/backoff" + "github.com/gravitational/trace" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/jonboulle/clockwork" + + schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/workloadidentity/v1" +) + +// resourceTeleportWorkloadIdentityType is the resource metadata type +type resourceTeleportWorkloadIdentityType struct{} + +// resourceTeleportWorkloadIdentity is the resource +type resourceTeleportWorkloadIdentity struct { + p Provider +} + +// GetSchema returns the resource schema +func (r resourceTeleportWorkloadIdentityType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return schemav1.GenSchemaWorkloadIdentity(ctx) +} + +// NewResource creates the empty resource +func (r resourceTeleportWorkloadIdentityType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceTeleportWorkloadIdentity{ + p: *(p.(*Provider)), + }, nil +} + +// Create creates the WorkloadIdentity +func (r resourceTeleportWorkloadIdentity) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { + var err error + if !r.p.IsConfigured(resp.Diagnostics) { + return + } + + var plan types.Object + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + workloadIdentity := &workloadidentityv1.WorkloadIdentity{} + diags = schemav1.CopyWorkloadIdentityFromTerraform(ctx, plan, workloadIdentity) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + + workloadIdentityResource := workloadIdentity + + workloadIdentityResource.Kind = "workload_identity" + + id := workloadIdentityResource.Metadata.Name + + _, err = r.p.Client.GetWorkloadIdentity(ctx, id) + if !trace.IsNotFound(err) { + if err == nil { + existErr := fmt.Sprintf("WorkloadIdentity exists in Teleport. Either remove it (tctl rm workload_identity/%v)"+ + " or import it to the existing state (terraform import teleport_workload_identity.%v %v)", id, id, id) + + resp.Diagnostics.Append(diagFromErr("WorkloadIdentity exists in Teleport", trace.Errorf(existErr))) + return + } + + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + _, err = r.p.Client.CreateWorkloadIdentity(ctx, workloadIdentityResource) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error creating WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + var workloadIdentityI *workloadidentityv1.WorkloadIdentity + tries := 0 + backoff := backoff.NewDecorr(r.p.RetryConfig.Base, r.p.RetryConfig.Cap, clockwork.NewRealClock()) + for { + tries = tries + 1 + workloadIdentityI, err = r.p.Client.GetWorkloadIdentity(ctx, id) + if trace.IsNotFound(err) { + if bErr := backoff.Do(ctx); bErr != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(bErr), "workload_identity")) + return + } + if tries >= r.p.RetryConfig.MaxTries { + diagMessage := fmt.Sprintf("Error reading WorkloadIdentity (tried %d times) - state outdated, please import resource", tries) + resp.Diagnostics.AddError(diagMessage, "workload_identity") + } + continue + } + break + } + + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + workloadIdentityResource = workloadIdentityI + + workloadIdentity = workloadIdentityResource + + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentity, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + plan.Attrs["id"] = types.String{Value: workloadIdentity.Metadata.Name} + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read reads teleport WorkloadIdentity +func (r resourceTeleportWorkloadIdentity) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { + var state types.Object + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var id types.String + diags = req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + workloadIdentityI, err := r.p.Client.GetWorkloadIdentity(ctx, id.Value) + if trace.IsNotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + workloadIdentity := workloadIdentityI + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentity, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update updates teleport WorkloadIdentity +func (r resourceTeleportWorkloadIdentity) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { + if !r.p.IsConfigured(resp.Diagnostics) { + return + } + + var plan types.Object + diags := req.Plan.Get(ctx, &plan) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + workloadIdentity := &workloadidentityv1.WorkloadIdentity{} + diags = schemav1.CopyWorkloadIdentityFromTerraform(ctx, plan, workloadIdentity) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + workloadIdentityResource := workloadIdentity + + + + name := workloadIdentityResource.Metadata.Name + + workloadIdentityBefore, err := r.p.Client.GetWorkloadIdentity(ctx, name) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", err, "workload_identity")) + return + } + + _, err = r.p.Client.UpsertWorkloadIdentity(ctx, workloadIdentityResource) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error updating WorkloadIdentity", err, "workload_identity")) + return + } + var workloadIdentityI *workloadidentityv1.WorkloadIdentity + + tries := 0 + backoff := backoff.NewDecorr(r.p.RetryConfig.Base, r.p.RetryConfig.Cap, clockwork.NewRealClock()) + for { + tries = tries + 1 + workloadIdentityI, err = r.p.Client.GetWorkloadIdentity(ctx, name) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", err, "workload_identity")) + return + } + if workloadIdentityBefore.GetMetadata().Revision != workloadIdentityI.GetMetadata().Revision || false { + break + } + + if err := backoff.Do(ctx); err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + if tries >= r.p.RetryConfig.MaxTries { + diagMessage := fmt.Sprintf("Error reading WorkloadIdentity (tried %d times) - state outdated, please import resource", tries) + resp.Diagnostics.AddError(diagMessage, "workload_identity") + return + } + } + + workloadIdentityResource = workloadIdentityI + + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentity, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes Teleport WorkloadIdentity +func (r resourceTeleportWorkloadIdentity) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { + var id types.String + diags := req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := r.p.Client.DeleteWorkloadIdentity(ctx, id.Value) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error deleting WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + resp.State.RemoveResource(ctx) +} + +// ImportState imports WorkloadIdentity state +func (r resourceTeleportWorkloadIdentity) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + workloadIdentity, err := r.p.Client.GetWorkloadIdentity(ctx, req.ID) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading WorkloadIdentity", trace.Wrap(err), "workload_identity")) + return + } + + workloadIdentityResource := workloadIdentity + + + var state types.Object + + diags := resp.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = schemav1.CopyWorkloadIdentityToTerraform(ctx, workloadIdentityResource, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + id := workloadIdentity.Metadata.Name + + state.Attrs["id"] = types.String{Value: id} + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf b/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf new file mode 100644 index 0000000000000..b5d0ebe8aae08 --- /dev/null +++ b/integrations/terraform/testlib/fixtures/workload_identity_0_create.tf @@ -0,0 +1,21 @@ +resource "teleport_workload_identity" "test" { + version = "v1" + metadata = { + name = "test" + } + spec = { + rules = { + allow = [ + { + conditions = [{ + attribute = "user.name" + equals = "foo" + }] + } + ] + } + spiffe = { + id = "/test" + } + } +} \ No newline at end of file diff --git a/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf b/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf new file mode 100644 index 0000000000000..cced0a4f8ecdd --- /dev/null +++ b/integrations/terraform/testlib/fixtures/workload_identity_1_update.tf @@ -0,0 +1,21 @@ +resource "teleport_workload_identity" "test" { + version = "v1" + metadata = { + name = "test" + } + spec = { + rules = { + allow = [ + { + conditions = [{ + attribute = "user.name" + equals = "foo" + }] + } + ] + } + spiffe = { + id = "/test/updated" + } + } +} \ No newline at end of file diff --git a/integrations/terraform/testlib/workload_identity_test.go b/integrations/terraform/testlib/workload_identity_test.go new file mode 100644 index 0000000000000..1e6d84cf6feb9 --- /dev/null +++ b/integrations/terraform/testlib/workload_identity_test.go @@ -0,0 +1,143 @@ +// Copyright 2024 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testlib + +import ( + "context" + "fmt" + "time" + + "github.com/gravitational/trace" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/require" + + v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/api/types" +) + +func (s *TerraformSuiteOSS) TestWorkloadIdentity() { + t := s.T() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + checkDestroyed := func(state *terraform.State) error { + _, err := s.client.GetWorkloadIdentity(ctx, "test") + if trace.IsNotFound(err) { + return nil + } + return trace.Errorf("expected not found, actual: %v", err) + } + + name := "teleport_workload_identity.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkDestroyed, + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: s.getFixture("workload_identity_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", "workload_identity"), + resource.TestCheckResourceAttr(name, "spec.spiffe.id", "/test"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.attribute", "user.name"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.equals", "foo"), + ), + }, + { + Config: s.getFixture("workload_identity_0_create.tf"), + PlanOnly: true, + }, + { + Config: s.getFixture("workload_identity_1_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", "workload_identity"), + resource.TestCheckResourceAttr(name, "spec.spiffe.id", "/test/updated"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.attribute", "user.name"), + resource.TestCheckResourceAttr(name, "spec.rules.allow.0.conditions.0.equals", "foo"), + ), + }, + { + Config: s.getFixture("workload_identity_1_update.tf"), + PlanOnly: true, + }, + }, + }) +} + +func (s *TerraformSuiteOSS) TestImportWorkloadIdentity() { + t := s.T() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + r := "teleport_workload_identity" + id := "test_import" + name := r + "." + id + + shu := &workloadidentityv1pb.WorkloadIdentity{ + Metadata: &v1.Metadata{ + Name: id, + }, + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "user.name", + Equals: "foo", + }, + }, + }, + }, + }, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/test", + }, + }, + } + shu, err := s.client.CreateWorkloadIdentity(ctx, shu) + require.NoError(t, err) + + require.Eventually(t, func() bool { + _, err := s.client.GetWorkloadIdentity(ctx, shu.GetMetadata().Name) + return err == nil + }, 5*time.Second, time.Second) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf("%s\nresource %q %q { }", s.terraformConfig, r, id), + ResourceName: name, + ImportState: true, + ImportStateId: id, + ImportStateCheck: func(state []*terraform.InstanceState) error { + require.Equal(t, types.KindWorkloadIdentity, state[0].Attributes["kind"]) + require.Equal(t, "/test", state[0].Attributes["spec.spiffe.id"]) + require.Equal(t, "user.name", state[0].Attributes["spec.rules.allow.0.conditions.0.attribute"]) + require.Equal(t, "foo", state[0].Attributes["spec.rules.allow.0.conditions.0.equals"]) + + return nil + }, + }, + }, + }) +} diff --git a/integrations/terraform/tfschema/workloadidentity/v1/custom_types.go b/integrations/terraform/tfschema/workloadidentity/v1/custom_types.go new file mode 100644 index 0000000000000..eb615d5b1888b --- /dev/null +++ b/integrations/terraform/tfschema/workloadidentity/v1/custom_types.go @@ -0,0 +1,25 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package v1 + +import "github.com/gravitational/teleport/integrations/terraform/tfschema/resource153" + +var ( + GenSchemaTimestamp = resource153.GenSchemaTimestamp + CopyToTimestamp = resource153.CopyToTimestamp + CopyFromTimestamp = resource153.CopyFromTimestamp +) diff --git a/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go new file mode 100644 index 0000000000000..f6510d28f7294 --- /dev/null +++ b/integrations/terraform/tfschema/workloadidentity/v1/resource_terraform.go @@ -0,0 +1,1177 @@ +/* +Copyright 2015-2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: teleport/workloadidentity/v1/resource.proto + +package v1 + +import ( + context "context" + fmt "fmt" + math "math" + + proto "github.com/gogo/protobuf/proto" + _ "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + github_com_gravitational_teleport_api_gen_proto_go_teleport_header_v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + github_com_gravitational_teleport_integrations_terraform_tfschema "github.com/gravitational/teleport/integrations/terraform/tfschema" + github_com_hashicorp_terraform_plugin_framework_attr "github.com/hashicorp/terraform-plugin-framework/attr" + github_com_hashicorp_terraform_plugin_framework_diag "github.com/hashicorp/terraform-plugin-framework/diag" + github_com_hashicorp_terraform_plugin_framework_tfsdk "github.com/hashicorp/terraform-plugin-framework/tfsdk" + github_com_hashicorp_terraform_plugin_framework_types "github.com/hashicorp/terraform-plugin-framework/types" + github_com_hashicorp_terraform_plugin_go_tftypes "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// GenSchemaWorkloadIdentity returns tfsdk.Schema definition for WorkloadIdentity +func GenSchemaWorkloadIdentity(ctx context.Context) (github_com_hashicorp_terraform_plugin_framework_tfsdk.Schema, github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics) { + return github_com_hashicorp_terraform_plugin_framework_tfsdk.Schema{Attributes: map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "id": { + Computed: true, + Optional: false, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Required: false, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "kind": { + Computed: true, + Description: "The kind of resource represented.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "metadata": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "description": { + Description: "description is object description.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "expires": GenSchemaTimestamp(ctx, github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + Description: "expires is a global expiry time header can be set on any resource in the system.", + Optional: true, + Validators: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributeValidator{github_com_gravitational_teleport_integrations_terraform_tfschema.MustTimeBeInFuture()}, + }), + "labels": { + Description: "labels is a set of labels.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.MapType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }, + "name": { + Description: "name is an object name.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "namespace": { + Computed: true, + Description: "namespace is object namespace. The field should be called \"namespace\" when it returns in Teleport 2.4.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "revision": { + Description: "revision is an opaque identifier which tracks the versions of a resource over time. Clients should ignore and not alter its value but must return the revision in any updates of a resource.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Common metadata that all resources share.", + Optional: true, + }, + "spec": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "rules": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"allow": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"conditions": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "attribute": { + Description: "The name of the attribute to evaluate the condition against.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "equals": { + Description: "An exact string that the attribute must match.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "The conditions that must be met for this rule to be considered passed.", + Optional: true, + }}), + Description: "A list of rules used to determine if a WorkloadIdentity can be issued. If none are provided, it will be considered a pass. If any are provided, then at least one must pass for the rules to be considered passed.", + Optional: true, + }}), + Description: "The rules which are evaluated before the WorkloadIdentity can be issued.", + Optional: true, + }, + "spiffe": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "hint": { + Description: "A freeform text field which is provided to workloads along with a credential produced by this WorkloadIdentity. This can be used to provide additional context that can be used to select between multiple credentials.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "id": { + Description: "The path of the SPIFFE ID that will be issued to the workload. This should be prefixed with a forward-slash (\"/\"). This field supports templating using attributes.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Configuration pertaining to the issuance of SPIFFE-compatible workload identity credentials.", + Optional: true, + }, + }), + Description: "The configured properties of the WorkloadIdentity", + Optional: true, + }, + "sub_kind": { + Description: "Differentiates variations of the same kind. All resources should contain one, even if it is never populated.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "version": { + Description: "The version of the resource being represented.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }}, nil +} + +// CopyWorkloadIdentityFromTerraform copies contents of the source Terraform object into a target struct +func CopyWorkloadIdentityFromTerraform(_ context.Context, tf github_com_hashicorp_terraform_plugin_framework_types.Object, obj *github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentity) github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics { + var diags github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics + { + a, ok := tf.Attrs["kind"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.kind"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Kind = t + } + } + } + { + a, ok := tf.Attrs["sub_kind"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.sub_kind"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.sub_kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.SubKind = t + } + } + } + { + a, ok := tf.Attrs["version"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.version"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.version", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Version = t + } + } + } + { + a, ok := tf.Attrs["metadata"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Metadata = nil + if !v.Null && !v.Unknown { + tf := v + obj.Metadata = &github_com_gravitational_teleport_api_gen_proto_go_teleport_header_v1.Metadata{} + obj := obj.Metadata + { + a, ok := tf.Attrs["name"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.name"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Name = t + } + } + } + { + a, ok := tf.Attrs["namespace"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.namespace"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.namespace", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Namespace = t + } + } + } + { + a, ok := tf.Attrs["description"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.description"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.description", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Description = t + } + } + } + { + a, ok := tf.Attrs["labels"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.labels"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Map) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.Map"}) + } else { + obj.Labels = make(map[string]string, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.labels", "github_com_hashicorp_terraform_plugin_framework_types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Labels[k] = t + } + } + } + } + } + } + { + a, ok := tf.Attrs["expires"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.expires"}) + } + CopyFromTimestamp(diags, a, &obj.Expires) + } + { + a, ok := tf.Attrs["revision"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.metadata.revision"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.metadata.revision", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Revision = t + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["spec"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Spec = nil + if !v.Null && !v.Unknown { + tf := v + obj.Spec = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentitySpec{} + obj := obj.Spec + { + a, ok := tf.Attrs["rules"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Rules = nil + if !v.Null && !v.Unknown { + tf := v + obj.Rules = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityRules{} + obj := obj.Rules + { + a, ok := tf.Attrs["allow"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.Allow = make([]*github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityRule, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow", "github_com_hashicorp_terraform_plugin_framework_types.Object"}) + } else { + var t *github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityRule + if !v.Null && !v.Unknown { + tf := v + t = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityRule{} + obj := t + { + a, ok := tf.Attrs["conditions"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.Conditions = make([]*github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions", "github_com_hashicorp_terraform_plugin_framework_types.Object"}) + } else { + var t *github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition + if !v.Null && !v.Unknown { + tf := v + t = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentityCondition{} + obj := t + { + a, ok := tf.Attrs["attribute"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.attribute"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.attribute", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Attribute = t + } + } + } + { + a, ok := tf.Attrs["equals"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Equals = t + } + } + } + } + obj.Conditions[k] = t + } + } + } + } + } + } + } + obj.Allow[k] = t + } + } + } + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["spiffe"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Spiffe = nil + if !v.Null && !v.Unknown { + tf := v + obj.Spiffe = &github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentitySPIFFE{} + obj := obj.Spiffe + { + a, ok := tf.Attrs["id"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe.id"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe.id", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Id = t + } + } + } + { + a, ok := tf.Attrs["hint"] + if !ok { + diags.Append(attrReadMissingDiag{"WorkloadIdentity.spec.spiffe.hint"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"WorkloadIdentity.spec.spiffe.hint", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Hint = t + } + } + } + } + } + } + } + } + } + } + } + return diags +} + +// CopyWorkloadIdentityToTerraform copies contents of the source Terraform object into a target struct +func CopyWorkloadIdentityToTerraform(ctx context.Context, obj *github_com_gravitational_teleport_api_gen_proto_go_teleport_workloadidentity_v1.WorkloadIdentity, tf *github_com_hashicorp_terraform_plugin_framework_types.Object) github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics { + var diags github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics + tf.Null = false + tf.Unknown = false + if tf.Attrs == nil { + tf.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value) + } + { + t, ok := tf.AttrTypes["kind"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.kind"}) + } else { + v, ok := tf.Attrs["kind"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Kind) == "" + } + v.Value = string(obj.Kind) + v.Unknown = false + tf.Attrs["kind"] = v + } + } + { + t, ok := tf.AttrTypes["sub_kind"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.sub_kind"}) + } else { + v, ok := tf.Attrs["sub_kind"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.sub_kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.sub_kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.SubKind) == "" + } + v.Value = string(obj.SubKind) + v.Unknown = false + tf.Attrs["sub_kind"] = v + } + } + { + t, ok := tf.AttrTypes["version"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.version"}) + } else { + v, ok := tf.Attrs["version"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.version", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.version", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Version) == "" + } + v.Value = string(obj.Version) + v.Unknown = false + tf.Attrs["version"] = v + } + } + { + a, ok := tf.AttrTypes["metadata"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["metadata"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.Metadata == nil { + v.Null = true + } else { + obj := obj.Metadata + tf := &v + { + t, ok := tf.AttrTypes["name"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.name"}) + } else { + v, ok := tf.Attrs["name"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.name", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Name) == "" + } + v.Value = string(obj.Name) + v.Unknown = false + tf.Attrs["name"] = v + } + } + { + t, ok := tf.AttrTypes["namespace"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.namespace"}) + } else { + v, ok := tf.Attrs["namespace"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.namespace", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.namespace", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Namespace) == "" + } + v.Value = string(obj.Namespace) + v.Unknown = false + tf.Attrs["namespace"] = v + } + } + { + t, ok := tf.AttrTypes["description"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.description"}) + } else { + v, ok := tf.Attrs["description"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.description", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.description", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Description) == "" + } + v.Value = string(obj.Description) + v.Unknown = false + tf.Attrs["description"] = v + } + } + { + a, ok := tf.AttrTypes["labels"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.labels"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.MapType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.MapType"}) + } else { + c, ok := tf.Attrs["labels"].(github_com_hashicorp_terraform_plugin_framework_types.Map) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.Map{ + + ElemType: o.ElemType, + Elems: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Labels)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Labels)) + } + } + if obj.Labels != nil { + t := o.ElemType + for k, a := range obj.Labels { + v, ok := tf.Attrs["labels"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.labels", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.labels", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = false + } + v.Value = string(a) + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Labels) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["labels"] = c + } + } + } + { + t, ok := tf.AttrTypes["expires"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.expires"}) + } else { + v := CopyToTimestamp(diags, obj.Expires, t, tf.Attrs["expires"]) + tf.Attrs["expires"] = v + } + } + { + t, ok := tf.AttrTypes["revision"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.metadata.revision"}) + } else { + v, ok := tf.Attrs["revision"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.metadata.revision", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.metadata.revision", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Revision) == "" + } + v.Value = string(obj.Revision) + v.Unknown = false + tf.Attrs["revision"] = v + } + } + } + v.Unknown = false + tf.Attrs["metadata"] = v + } + } + } + { + a, ok := tf.AttrTypes["spec"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["spec"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.Spec == nil { + v.Null = true + } else { + obj := obj.Spec + tf := &v + { + a, ok := tf.AttrTypes["rules"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["rules"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.Rules == nil { + v.Null = true + } else { + obj := obj.Rules + tf := &v + { + a, ok := tf.AttrTypes["allow"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["allow"].(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.List{ + + ElemType: o.ElemType, + Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Allow)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Allow)) + } + } + if obj.Allow != nil { + o := o.ElemType.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if len(obj.Allow) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Allow)) + } + for k, a := range obj.Allow { + v, ok := tf.Attrs["allow"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if a == nil { + v.Null = true + } else { + obj := a + tf := &v + { + a, ok := tf.AttrTypes["conditions"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["conditions"].(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.List{ + + ElemType: o.ElemType, + Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Conditions)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Conditions)) + } + } + if obj.Conditions != nil { + o := o.ElemType.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if len(obj.Conditions) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Conditions)) + } + for k, a := range obj.Conditions { + v, ok := tf.Attrs["conditions"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if a == nil { + v.Null = true + } else { + obj := a + tf := &v + { + t, ok := tf.AttrTypes["attribute"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.attribute"}) + } else { + v, ok := tf.Attrs["attribute"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.rules.allow.conditions.attribute", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.attribute", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Attribute) == "" + } + v.Value = string(obj.Attribute) + v.Unknown = false + tf.Attrs["attribute"] = v + } + } + { + t, ok := tf.AttrTypes["equals"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals"}) + } else { + v, ok := tf.Attrs["equals"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.rules.allow.conditions.equals", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.rules.allow.conditions.equals", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Equals) == "" + } + v.Value = string(obj.Equals) + v.Unknown = false + tf.Attrs["equals"] = v + } + } + } + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Conditions) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["conditions"] = c + } + } + } + } + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Allow) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["allow"] = c + } + } + } + } + v.Unknown = false + tf.Attrs["rules"] = v + } + } + } + { + a, ok := tf.AttrTypes["spiffe"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["spiffe"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.Spiffe == nil { + v.Null = true + } else { + obj := obj.Spiffe + tf := &v + { + t, ok := tf.AttrTypes["id"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe.id"}) + } else { + v, ok := tf.Attrs["id"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.spiffe.id", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe.id", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Id) == "" + } + v.Value = string(obj.Id) + v.Unknown = false + tf.Attrs["id"] = v + } + } + { + t, ok := tf.AttrTypes["hint"] + if !ok { + diags.Append(attrWriteMissingDiag{"WorkloadIdentity.spec.spiffe.hint"}) + } else { + v, ok := tf.Attrs["hint"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"WorkloadIdentity.spec.spiffe.hint", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"WorkloadIdentity.spec.spiffe.hint", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Hint) == "" + } + v.Value = string(obj.Hint) + v.Unknown = false + tf.Attrs["hint"] = v + } + } + } + v.Unknown = false + tf.Attrs["spiffe"] = v + } + } + } + } + v.Unknown = false + tf.Attrs["spec"] = v + } + } + } + return diags +} + +// attrReadMissingDiag represents diagnostic message on an attribute missing in the source object +type attrReadMissingDiag struct { + Path string +} + +func (d attrReadMissingDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrReadMissingDiag) Summary() string { + return "Error reading from Terraform object" +} + +func (d attrReadMissingDiag) Detail() string { + return fmt.Sprintf("A value for %v is missing in the source Terraform object Attrs", d.Path) +} + +func (d attrReadMissingDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrReadConversionFailureDiag represents diagnostic message on a failed type conversion on read +type attrReadConversionFailureDiag struct { + Path string + Type string +} + +func (d attrReadConversionFailureDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrReadConversionFailureDiag) Summary() string { + return "Error reading from Terraform object" +} + +func (d attrReadConversionFailureDiag) Detail() string { + return fmt.Sprintf("A value for %v can not be converted to %v", d.Path, d.Type) +} + +func (d attrReadConversionFailureDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteMissingDiag represents diagnostic message on an attribute missing in the target object +type attrWriteMissingDiag struct { + Path string +} + +func (d attrWriteMissingDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteMissingDiag) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteMissingDiag) Detail() string { + return fmt.Sprintf("A value for %v is missing in the source Terraform object AttrTypes", d.Path) +} + +func (d attrWriteMissingDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteConversionFailureDiag represents diagnostic message on a failed type conversion on write +type attrWriteConversionFailureDiag struct { + Path string + Type string +} + +func (d attrWriteConversionFailureDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteConversionFailureDiag) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteConversionFailureDiag) Detail() string { + return fmt.Sprintf("A value for %v can not be converted to %v", d.Path, d.Type) +} + +func (d attrWriteConversionFailureDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteGeneralError represents diagnostic message on a generic error on write +type attrWriteGeneralError struct { + Path string + Err error +} + +func (d attrWriteGeneralError) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteGeneralError) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteGeneralError) Detail() string { + return fmt.Sprintf("%s: %s", d.Path, d.Err.Error()) +} + +func (d attrWriteGeneralError) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} From d22dca38483ba9bec45d514692018075284f3e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Mon, 30 Dec 2024 16:22:57 +0100 Subject: [PATCH 10/30] [v17] Add info about MFA methods to user reset modal (#50607) * Rewrite story to use controls * Add info about MFA methods --- .../src/Users/UserReset/UserReset.story.tsx | 81 +++++++++++++------ .../src/Users/UserReset/UserReset.tsx | 36 ++++++--- 2 files changed, 81 insertions(+), 36 deletions(-) diff --git a/web/packages/teleport/src/Users/UserReset/UserReset.story.tsx b/web/packages/teleport/src/Users/UserReset/UserReset.story.tsx index fd74a04b68ded..3a68f43ac6a8f 100644 --- a/web/packages/teleport/src/Users/UserReset/UserReset.story.tsx +++ b/web/packages/teleport/src/Users/UserReset/UserReset.story.tsx @@ -16,40 +16,69 @@ * along with this program. If not, see . */ +import { Meta } from '@storybook/react'; + +import { Attempt } from 'shared/hooks/useAttemptNext'; +import { useEffect, useState } from 'react'; + +import cfg from 'teleport/config'; + import { UserReset } from './UserReset'; -export default { - title: 'Teleport/Users/UserReset', +type StoryProps = { + status: 'processing' | 'success' | 'error'; + isMfaEnabled: boolean; + allowPasswordless: boolean; }; -export const Processing = () => { - return ; +const meta: Meta = { + title: 'Teleport/Users/UserReset', + component: Story, + argTypes: { + status: { + control: { type: 'radio' }, + options: ['processing', 'success', 'error'], + }, + }, + args: { + status: 'processing', + isMfaEnabled: true, + allowPasswordless: true, + }, }; -export const Success = () => { - return ; -}; +export default meta; + +export function Story(props: StoryProps) { + const statusToAttempt: Record = { + processing: { status: 'processing' }, + success: { status: 'success' }, + error: { status: 'failed', statusText: 'some server error' }, + }; + const [, setState] = useState({}); + + useEffect(() => { + const defaultAuth = structuredClone(cfg.auth); + cfg.auth.second_factor = props.isMfaEnabled ? 'on' : 'off'; + cfg.auth.allowPasswordless = props.allowPasswordless; + setState({}); // Force re-render of the component with new cfg. + + return () => { + cfg.auth = defaultAuth; + }; + }, [props.isMfaEnabled, props.allowPasswordless]); -export const Failed = () => { return ( {}} + onClose={() => {}} + attempt={statusToAttempt[props.status]} /> ); -}; - -const props = { - username: 'smith', - token: { - value: '0c536179038b386728dfee6602ca297f', - expires: new Date('2021-04-08T07:30:00Z'), - username: 'Lester', - }, - onReset() {}, - onClose() {}, - attempt: { - status: 'processing', - statusText: '', - }, -}; +} diff --git a/web/packages/teleport/src/Users/UserReset/UserReset.tsx b/web/packages/teleport/src/Users/UserReset/UserReset.tsx index 65e15b1a2f2c2..0eb11518a0fb5 100644 --- a/web/packages/teleport/src/Users/UserReset/UserReset.tsx +++ b/web/packages/teleport/src/Users/UserReset/UserReset.tsx @@ -17,7 +17,7 @@ */ import { useState } from 'react'; -import { ButtonPrimary, ButtonSecondary, Text, Alert } from 'design'; +import { ButtonPrimary, ButtonSecondary, Text, Alert, P2 } from 'design'; import Dialog, { DialogHeader, DialogTitle, @@ -26,6 +26,7 @@ import Dialog, { } from 'design/Dialog'; import { useAttemptNext } from 'shared/hooks'; +import cfg from 'teleport/config'; import { ResetToken } from 'teleport/services/user'; import UserTokenLink from './../UserTokenLink'; @@ -58,16 +59,31 @@ export function UserReset({ {attempt.status === 'failed' && ( - + {attempt.statusText} )} - - You are about to reset authentication for user - - {` ${username} `} + + You are about to reset authentication for user{' '} + + {username} - . This will generate a temporary URL which can be used to set up new - authentication. - + . This will generate a temporary URL which can be used to set up + new authentication. + + {cfg.isMfaEnabled() && ( + + All{' '} + {cfg.isPasswordlessEnabled() + ? 'passkeys and MFA methods' + : 'MFA methods'}{' '} + of this user will be removed. The user will be able to set up{' '} + {cfg.isPasswordlessEnabled() ? ( + <>a new passkey or an MFA method + ) : ( + <>a new method + )}{' '} + after following the URL. + + )} - Generate reset url + Generate Reset URL Cancel From 2b88ebcf52ea0b675a70fd8300d2357923ced9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Mon, 30 Dec 2024 16:22:57 +0100 Subject: [PATCH 11/30] [v17] Add info about MFA methods to user reset modal (#50607) * Rewrite story to use controls * Add info about MFA methods From 10a8072d6243829178bd2f91fe4663dff6c32d9a Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Mon, 30 Dec 2024 08:36:15 -0700 Subject: [PATCH 12/30] [v17] Add a null check to `getMfaChallengeResponse` (#50611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a null check to getMfaChallengeResponse. * Add other null checks, just in case. * Update signatures to include undefined, consistently return undefined --------- Co-authored-by: joerger Co-authored-by: RafaÅ‚ CieÅ›lak --- .../ChangePasswordWizard/ChangePasswordWizard.tsx | 2 +- web/packages/teleport/src/lib/term/tty.ts | 6 +++--- web/packages/teleport/src/services/auth/auth.ts | 10 ++++++---- web/packages/teleport/src/services/mfa/makeMfa.ts | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/web/packages/teleport/src/Account/ChangePasswordWizard/ChangePasswordWizard.tsx b/web/packages/teleport/src/Account/ChangePasswordWizard/ChangePasswordWizard.tsx index a5eb1e8fc7204..4d4f40a4f49c4 100644 --- a/web/packages/teleport/src/Account/ChangePasswordWizard/ChangePasswordWizard.tsx +++ b/web/packages/teleport/src/Account/ChangePasswordWizard/ChangePasswordWizard.tsx @@ -65,7 +65,7 @@ export function ChangePasswordWizard({ const reauthState = useReAuthenticate({ challengeScope: MfaChallengeScope.CHANGE_PASSWORD, onMfaResponse: async mfaResponse => - setWebauthnResponse(mfaResponse.webauthn_response), + setWebauthnResponse(mfaResponse?.webauthn_response), }); const [reauthMethod, setReauthMethod] = useState(); diff --git a/web/packages/teleport/src/lib/term/tty.ts b/web/packages/teleport/src/lib/term/tty.ts index 3e924ff466f3f..7c50a38ee9a38 100644 --- a/web/packages/teleport/src/lib/term/tty.ts +++ b/web/packages/teleport/src/lib/term/tty.ts @@ -82,7 +82,7 @@ class Tty extends EventEmitterMfaSender { this.socket.send(bytearray.buffer); } - sendChallengeResponse(data: MfaChallengeResponse) { + sendChallengeResponse(resp: MfaChallengeResponse) { // we want to have the backend listen on a single message type // for any responses. so our data will look like data.webauthn, data.sso, etc // but to be backward compatible, we need to still spread the existing webauthn only fields @@ -90,8 +90,8 @@ class Tty extends EventEmitterMfaSender { // in 19, we can just pass "data" without this extra step // TODO (avatus): DELETE IN 18 const backwardCompatibleData = { - ...data.webauthn_response, - ...data, + ...resp?.webauthn_response, + ...resp, }; const encoded = this._proto.encodeChallengeResponse( JSON.stringify(backwardCompatibleData) diff --git a/web/packages/teleport/src/services/auth/auth.ts b/web/packages/teleport/src/services/auth/auth.ts index 3724f1dc8b056..e1533ee4d3297 100644 --- a/web/packages/teleport/src/services/auth/auth.ts +++ b/web/packages/teleport/src/services/auth/auth.ts @@ -239,7 +239,7 @@ const auth = { .then(res => { const request = { action: 'accept', - webauthnAssertionResponse: res.webauthn_response, + webauthnAssertionResponse: res?.webauthn_response, }; return api.put(cfg.getHeadlessSsoPath(transactionId), request); @@ -280,7 +280,9 @@ const auth = { challenge: MfaAuthenticateChallenge, mfaType?: DeviceType, totpCode?: string - ): Promise { + ): Promise { + if (!challenge) return; + // TODO(Joerger): If mfaType is not provided by a parent component, use some global context // to display a component, similar to the one used in useMfa. For now we just default to // whichever method we can succeed with first. @@ -303,7 +305,7 @@ const auth = { } // No viable challenge, return empty response. - return null; + return; }, async getWebAuthnChallengeResponse( @@ -387,7 +389,7 @@ const auth = { return auth .getMfaChallenge({ scope, allowReuse, isMfaRequiredRequest }, abortSignal) .then(challenge => auth.getMfaChallengeResponse(challenge, 'webauthn')) - .then(res => res.webauthn_response); + .then(res => res?.webauthn_response); }, getMfaChallengeResponseForAdminAction(allowReuse?: boolean) { diff --git a/web/packages/teleport/src/services/mfa/makeMfa.ts b/web/packages/teleport/src/services/mfa/makeMfa.ts index 505a972fe33e5..4d98503dafa87 100644 --- a/web/packages/teleport/src/services/mfa/makeMfa.ts +++ b/web/packages/teleport/src/services/mfa/makeMfa.ts @@ -63,13 +63,13 @@ export function parseMfaRegistrationChallengeJson( // parseMfaChallengeJson formats fetched authenticate challenge JSON. export function parseMfaChallengeJson( challenge: MfaAuthenticateChallengeJson -): MfaAuthenticateChallenge { +): MfaAuthenticateChallenge | undefined { if ( !challenge.sso_challenge && !challenge.webauthn_challenge && !challenge.totp_challenge ) { - return null; + return; } // WebAuthn challenge contains Base64URL(byte) fields that needs to From 8cc32904b9b85a2bf65d4d0e9efe286528515651 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Mon, 30 Dec 2024 08:43:14 -0700 Subject: [PATCH 13/30] Fix capitalization on user reset button (#50610) URL is an acronym and should be capitalized. From 5274176d1eaa260619415ab24a5abf08739b3d4d Mon Sep 17 00:00:00 2001 From: Gus Rivera Date: Mon, 30 Dec 2024 10:22:47 -0600 Subject: [PATCH 14/30] Release 17.1.2 (#50612) --- CHANGELOG.md | 8 +++ Makefile | 2 +- api/version.go | 2 +- .../macos/tsh/tsh.app/Contents/Info.plist | 4 +- .../macos/tshdev/tsh.app/Contents/Info.plist | 4 +- e | 2 +- examples/chart/access/datadog/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 4 +- .../__snapshot__/deployment_test.yaml.snap | 8 +-- examples/chart/access/discord/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 4 +- .../__snapshot__/deployment_test.yaml.snap | 8 +-- examples/chart/access/email/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 24 +++---- .../__snapshot__/deployment_test.yaml.snap | 58 ++++++++-------- examples/chart/access/jira/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 4 +- .../__snapshot__/deployment_test.yaml.snap | 8 +-- examples/chart/access/mattermost/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 4 +- .../__snapshot__/deployment_test.yaml.snap | 28 ++++---- examples/chart/access/msteams/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 4 +- .../__snapshot__/deployment_test.yaml.snap | 8 +-- examples/chart/access/pagerduty/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 4 +- .../__snapshot__/deployment_test.yaml.snap | 8 +-- examples/chart/access/slack/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 4 +- .../__snapshot__/deployment_test.yaml.snap | 8 +-- examples/chart/event-handler/Chart.yaml | 2 +- .../__snapshot__/configmap_test.yaml.snap | 4 +- .../__snapshot__/deployment_test.yaml.snap | 6 +- examples/chart/tbot/Chart.yaml | 2 +- .../__snapshot__/deployment_test.yaml.snap | 8 +-- examples/chart/teleport-cluster/Chart.yaml | 2 +- .../charts/teleport-operator/Chart.yaml | 2 +- .../auth_clusterrole_test.yaml.snap | 4 +- .../__snapshot__/auth_config_test.yaml.snap | 4 +- .../auth_deployment_test.yaml.snap | 8 +-- .../__snapshot__/proxy_config_test.yaml.snap | 4 +- .../proxy_deployment_test.yaml.snap | 36 +++++----- examples/chart/teleport-kube-agent/Chart.yaml | 2 +- .../__snapshot__/deployment_test.yaml.snap | 60 ++++++++--------- .../tests/__snapshot__/job_test.yaml.snap | 10 +-- .../__snapshot__/statefulset_test.yaml.snap | 66 +++++++++---------- .../updater_deployment_test.yaml.snap | 4 +- 47 files changed, 228 insertions(+), 220 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da90069f11b80..a022e8c261068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 17.1.2 (12/30/24) + +* Fixed a bug in the WebUI that could cause an access denied error when accessing application. [#50611](https://github.com/gravitational/teleport/pull/50611) +* Improve session playback initial delay caused by an additional events query. [#50592](https://github.com/gravitational/teleport/pull/50592) +* Fix a bug in the `tbot` Helm chart causing invalid configuration when both default and custom outputs were used. [#50526](https://github.com/gravitational/teleport/pull/50526) +* Restore the ability to play session recordings in the web UI without specifying the session duration in the URL. [#50459](https://github.com/gravitational/teleport/pull/50459) +* Fix regression in `tbot` on Linux causing the Kubernetes credential helper to fail. [#50413](https://github.com/gravitational/teleport/pull/50413) + ## 17.1.1 (12/20/24) **Warning**: 17.1.1 fixes a regression in 17.1.0 that causes SSH server heartbeats diff --git a/Makefile b/Makefile index 62034396bdf86..9fafcd1fc6211 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ # Stable releases: "1.0.0" # Pre-releases: "1.0.0-alpha.1", "1.0.0-beta.2", "1.0.0-rc.3" # Master/dev branch: "1.0.0-dev" -VERSION=17.1.1 +VERSION=17.1.2 DOCKER_IMAGE ?= teleport diff --git a/api/version.go b/api/version.go index c1aa81f8bd305..caf6935ac7702 100644 --- a/api/version.go +++ b/api/version.go @@ -3,6 +3,6 @@ package api import "github.com/coreos/go-semver/semver" -const Version = "17.1.1" +const Version = "17.1.2" var SemVersion = semver.New(Version) diff --git a/build.assets/macos/tsh/tsh.app/Contents/Info.plist b/build.assets/macos/tsh/tsh.app/Contents/Info.plist index 53874cf22124e..5185e5c8c3f9c 100644 --- a/build.assets/macos/tsh/tsh.app/Contents/Info.plist +++ b/build.assets/macos/tsh/tsh.app/Contents/Info.plist @@ -19,13 +19,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - 17.1.1 + 17.1.2 CFBundleSupportedPlatforms MacOSX CFBundleVersion - 17.1.1 + 17.1.2 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild diff --git a/build.assets/macos/tshdev/tsh.app/Contents/Info.plist b/build.assets/macos/tshdev/tsh.app/Contents/Info.plist index 037752de74f48..f57cbff316910 100644 --- a/build.assets/macos/tshdev/tsh.app/Contents/Info.plist +++ b/build.assets/macos/tshdev/tsh.app/Contents/Info.plist @@ -17,13 +17,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - 17.1.1 + 17.1.2 CFBundleSupportedPlatforms MacOSX CFBundleVersion - 17.1.1 + 17.1.2 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild diff --git a/e b/e index fb6c1019bfc0b..fc7a7214b1fdc 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit fb6c1019bfc0b6887ff3cbbcc2367360810fa8a1 +Subproject commit fc7a7214b1fdcf7e6dd6b48a759247ac6355b417 diff --git a/examples/chart/access/datadog/Chart.yaml b/examples/chart/access/datadog/Chart.yaml index d8b05d6f6e8a3..af6d9934ce347 100644 --- a/examples/chart/access/datadog/Chart.yaml +++ b/examples/chart/access/datadog/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-datadog diff --git a/examples/chart/access/datadog/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/datadog/tests/__snapshot__/configmap_test.yaml.snap index 2e62fe34629f9..bb778d31c7594 100644 --- a/examples/chart/access/datadog/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/datadog/tests/__snapshot__/configmap_test.yaml.snap @@ -26,6 +26,6 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-datadog - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-datadog-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-datadog-17.1.2 name: RELEASE-NAME-teleport-plugin-datadog diff --git a/examples/chart/access/datadog/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/access/datadog/tests/__snapshot__/deployment_test.yaml.snap index ef7070fec6a91..e9a3fbd5cccf0 100644 --- a/examples/chart/access/datadog/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/access/datadog/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-datadog - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-datadog-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-datadog-17.1.2 name: RELEASE-NAME-teleport-plugin-datadog spec: replicas: 1 @@ -22,8 +22,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-datadog - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-datadog-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-datadog-17.1.2 spec: containers: - command: diff --git a/examples/chart/access/discord/Chart.yaml b/examples/chart/access/discord/Chart.yaml index 0a4e380e401c5..3f2d04cf8d01d 100644 --- a/examples/chart/access/discord/Chart.yaml +++ b/examples/chart/access/discord/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-discord diff --git a/examples/chart/access/discord/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/discord/tests/__snapshot__/configmap_test.yaml.snap index 101486bdbd696..eccbf15241262 100644 --- a/examples/chart/access/discord/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/discord/tests/__snapshot__/configmap_test.yaml.snap @@ -24,6 +24,6 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-discord - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-discord-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-discord-17.1.2 name: RELEASE-NAME-teleport-plugin-discord diff --git a/examples/chart/access/discord/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/access/discord/tests/__snapshot__/deployment_test.yaml.snap index fc2195ea55124..c22cdfe3b993d 100644 --- a/examples/chart/access/discord/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/access/discord/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-discord - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-discord-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-discord-17.1.2 name: RELEASE-NAME-teleport-plugin-discord spec: replicas: 1 @@ -22,8 +22,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-discord - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-discord-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-discord-17.1.2 spec: containers: - command: diff --git a/examples/chart/access/email/Chart.yaml b/examples/chart/access/email/Chart.yaml index 0e799a4f3699d..e377e7cffaf7f 100644 --- a/examples/chart/access/email/Chart.yaml +++ b/examples/chart/access/email/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-email diff --git a/examples/chart/access/email/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/email/tests/__snapshot__/configmap_test.yaml.snap index 089cfcbcc5633..2674924f7c3d9 100644 --- a/examples/chart/access/email/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/email/tests/__snapshot__/configmap_test.yaml.snap @@ -26,8 +26,8 @@ should match the snapshot (mailgun on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email should match the snapshot (smtp on): 1: | @@ -59,8 +59,8 @@ should match the snapshot (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email should match the snapshot (smtp on, no starttls): 1: | @@ -92,8 +92,8 @@ should match the snapshot (smtp on, no starttls): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email should match the snapshot (smtp on, password file): 1: | @@ -125,8 +125,8 @@ should match the snapshot (smtp on, password file): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email should match the snapshot (smtp on, roleToRecipients set): 1: | @@ -161,8 +161,8 @@ should match the snapshot (smtp on, roleToRecipients set): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email should match the snapshot (smtp on, starttls disabled): 1: | @@ -194,6 +194,6 @@ should match the snapshot (smtp on, starttls disabled): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email diff --git a/examples/chart/access/email/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/access/email/tests/__snapshot__/deployment_test.yaml.snap index 593aead992053..1b5a9b78de452 100644 --- a/examples/chart/access/email/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/access/email/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should be possible to override volume name (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email spec: replicas: 1 @@ -22,8 +22,8 @@ should be possible to override volume name (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 spec: containers: - command: @@ -34,7 +34,7 @@ should be possible to override volume name (smtp on): env: - name: TELEPORT_PLUGIN_FAIL_FAST value: "true" - image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.1 + image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.2 imagePullPolicy: IfNotPresent name: teleport-plugin-email ports: @@ -75,8 +75,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email spec: replicas: 1 @@ -90,8 +90,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 spec: containers: - command: @@ -136,8 +136,8 @@ should match the snapshot (mailgun on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email spec: replicas: 1 @@ -151,8 +151,8 @@ should match the snapshot (mailgun on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 spec: containers: - command: @@ -163,7 +163,7 @@ should match the snapshot (mailgun on): env: - name: TELEPORT_PLUGIN_FAIL_FAST value: "true" - image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.1 + image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.2 imagePullPolicy: IfNotPresent name: teleport-plugin-email ports: @@ -204,8 +204,8 @@ should match the snapshot (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email spec: replicas: 1 @@ -219,8 +219,8 @@ should match the snapshot (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 spec: containers: - command: @@ -231,7 +231,7 @@ should match the snapshot (smtp on): env: - name: TELEPORT_PLUGIN_FAIL_FAST value: "true" - image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.1 + image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.2 imagePullPolicy: IfNotPresent name: teleport-plugin-email ports: @@ -272,8 +272,8 @@ should mount external secret (mailgun on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email spec: replicas: 1 @@ -287,8 +287,8 @@ should mount external secret (mailgun on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 spec: containers: - command: @@ -299,7 +299,7 @@ should mount external secret (mailgun on): env: - name: TELEPORT_PLUGIN_FAIL_FAST value: "true" - image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.1 + image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.2 imagePullPolicy: IfNotPresent name: teleport-plugin-email ports: @@ -340,8 +340,8 @@ should mount external secret (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 name: RELEASE-NAME-teleport-plugin-email spec: replicas: 1 @@ -355,8 +355,8 @@ should mount external secret (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-email - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-email-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-email-17.1.2 spec: containers: - command: @@ -367,7 +367,7 @@ should mount external secret (smtp on): env: - name: TELEPORT_PLUGIN_FAIL_FAST value: "true" - image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.1 + image: public.ecr.aws/gravitational/teleport-plugin-email:17.1.2 imagePullPolicy: IfNotPresent name: teleport-plugin-email ports: diff --git a/examples/chart/access/jira/Chart.yaml b/examples/chart/access/jira/Chart.yaml index ed5a736b67d0f..6ca8dbd150f0f 100644 --- a/examples/chart/access/jira/Chart.yaml +++ b/examples/chart/access/jira/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-jira diff --git a/examples/chart/access/jira/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/jira/tests/__snapshot__/configmap_test.yaml.snap index 2a86bfa1c8f11..92eaa22d5cf2b 100644 --- a/examples/chart/access/jira/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/jira/tests/__snapshot__/configmap_test.yaml.snap @@ -32,6 +32,6 @@ should match the snapshot (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-jira - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-jira-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-jira-17.1.2 name: RELEASE-NAME-teleport-plugin-jira diff --git a/examples/chart/access/jira/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/access/jira/tests/__snapshot__/deployment_test.yaml.snap index 3ad0b2966319e..856b0f5ddf1c8 100644 --- a/examples/chart/access/jira/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/access/jira/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-jira - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-jira-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-jira-17.1.2 name: RELEASE-NAME-teleport-plugin-jira spec: replicas: 1 @@ -22,8 +22,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-jira - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-jira-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-jira-17.1.2 spec: containers: - command: diff --git a/examples/chart/access/mattermost/Chart.yaml b/examples/chart/access/mattermost/Chart.yaml index 668019b392ee5..67d7ea9172b2d 100644 --- a/examples/chart/access/mattermost/Chart.yaml +++ b/examples/chart/access/mattermost/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-mattermost diff --git a/examples/chart/access/mattermost/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/mattermost/tests/__snapshot__/configmap_test.yaml.snap index 927c8362c0568..10564216a75aa 100644 --- a/examples/chart/access/mattermost/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/mattermost/tests/__snapshot__/configmap_test.yaml.snap @@ -22,6 +22,6 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-mattermost - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-mattermost-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-mattermost-17.1.2 name: RELEASE-NAME-teleport-plugin-mattermost diff --git a/examples/chart/access/mattermost/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/access/mattermost/tests/__snapshot__/deployment_test.yaml.snap index cb11fe2f0f6ad..c7530391033a3 100644 --- a/examples/chart/access/mattermost/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/access/mattermost/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-mattermost - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-mattermost-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-mattermost-17.1.2 name: RELEASE-NAME-teleport-plugin-mattermost spec: replicas: 1 @@ -22,8 +22,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-mattermost - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-mattermost-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-mattermost-17.1.2 spec: containers: - command: @@ -75,8 +75,8 @@ should mount external secret: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-mattermost - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-mattermost-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-mattermost-17.1.2 name: RELEASE-NAME-teleport-plugin-mattermost spec: replicas: 1 @@ -90,8 +90,8 @@ should mount external secret: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-mattermost - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-mattermost-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-mattermost-17.1.2 spec: containers: - command: @@ -102,7 +102,7 @@ should mount external secret: env: - name: TELEPORT_PLUGIN_FAIL_FAST value: "true" - image: public.ecr.aws/gravitational/teleport-plugin-mattermost:17.1.1 + image: public.ecr.aws/gravitational/teleport-plugin-mattermost:17.1.2 imagePullPolicy: IfNotPresent name: teleport-plugin-mattermost ports: @@ -143,8 +143,8 @@ should override volume name: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-mattermost - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-mattermost-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-mattermost-17.1.2 name: RELEASE-NAME-teleport-plugin-mattermost spec: replicas: 1 @@ -158,8 +158,8 @@ should override volume name: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-mattermost - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-mattermost-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-mattermost-17.1.2 spec: containers: - command: @@ -170,7 +170,7 @@ should override volume name: env: - name: TELEPORT_PLUGIN_FAIL_FAST value: "true" - image: public.ecr.aws/gravitational/teleport-plugin-mattermost:17.1.1 + image: public.ecr.aws/gravitational/teleport-plugin-mattermost:17.1.2 imagePullPolicy: IfNotPresent name: teleport-plugin-mattermost ports: diff --git a/examples/chart/access/msteams/Chart.yaml b/examples/chart/access/msteams/Chart.yaml index 7d619ecaa796d..0e69391eb41e7 100644 --- a/examples/chart/access/msteams/Chart.yaml +++ b/examples/chart/access/msteams/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-msteams diff --git a/examples/chart/access/msteams/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/msteams/tests/__snapshot__/configmap_test.yaml.snap index 5d58bcf85cf9b..87d635f1df779 100644 --- a/examples/chart/access/msteams/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/msteams/tests/__snapshot__/configmap_test.yaml.snap @@ -29,6 +29,6 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-msteams - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-msteams-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-msteams-17.1.2 name: RELEASE-NAME-teleport-plugin-msteams diff --git a/examples/chart/access/msteams/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/access/msteams/tests/__snapshot__/deployment_test.yaml.snap index 86e0735d6208d..a6f05e09b8487 100644 --- a/examples/chart/access/msteams/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/access/msteams/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-msteams - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-msteams-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-msteams-17.1.2 name: RELEASE-NAME-teleport-plugin-msteams spec: replicas: 1 @@ -22,8 +22,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-msteams - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-msteams-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-msteams-17.1.2 spec: containers: - command: diff --git a/examples/chart/access/pagerduty/Chart.yaml b/examples/chart/access/pagerduty/Chart.yaml index 7125821ebdec8..9fa63eeebfae3 100644 --- a/examples/chart/access/pagerduty/Chart.yaml +++ b/examples/chart/access/pagerduty/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-pagerduty diff --git a/examples/chart/access/pagerduty/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/pagerduty/tests/__snapshot__/configmap_test.yaml.snap index 2bb96e917c515..9d5a6e9b6594a 100644 --- a/examples/chart/access/pagerduty/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/pagerduty/tests/__snapshot__/configmap_test.yaml.snap @@ -21,6 +21,6 @@ should match the snapshot (smtp on): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-pagerduty - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-pagerduty-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-pagerduty-17.1.2 name: RELEASE-NAME-teleport-plugin-pagerduty diff --git a/examples/chart/access/pagerduty/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/access/pagerduty/tests/__snapshot__/deployment_test.yaml.snap index 7172a804538c9..2c12e7939ac91 100644 --- a/examples/chart/access/pagerduty/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/access/pagerduty/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-pagerduty - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-pagerduty-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-pagerduty-17.1.2 name: RELEASE-NAME-teleport-plugin-pagerduty spec: replicas: 1 @@ -22,8 +22,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-pagerduty - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-pagerduty-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-pagerduty-17.1.2 spec: containers: - command: diff --git a/examples/chart/access/slack/Chart.yaml b/examples/chart/access/slack/Chart.yaml index b355632c95e8a..5a1a09786f2b8 100644 --- a/examples/chart/access/slack/Chart.yaml +++ b/examples/chart/access/slack/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-slack diff --git a/examples/chart/access/slack/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/slack/tests/__snapshot__/configmap_test.yaml.snap index 87b0400101a8a..b4f17d6aa8049 100644 --- a/examples/chart/access/slack/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/slack/tests/__snapshot__/configmap_test.yaml.snap @@ -24,6 +24,6 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-slack - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-slack-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-slack-17.1.2 name: RELEASE-NAME-teleport-plugin-slack diff --git a/examples/chart/access/slack/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/access/slack/tests/__snapshot__/deployment_test.yaml.snap index 1a72ab63e3523..a5111c834e4bd 100644 --- a/examples/chart/access/slack/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/access/slack/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-slack - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-slack-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-slack-17.1.2 name: RELEASE-NAME-teleport-plugin-slack spec: replicas: 1 @@ -22,8 +22,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-slack - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-slack-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-slack-17.1.2 spec: containers: - command: diff --git a/examples/chart/event-handler/Chart.yaml b/examples/chart/event-handler/Chart.yaml index a79de9a00ce4a..1a22e926fea6c 100644 --- a/examples/chart/event-handler/Chart.yaml +++ b/examples/chart/event-handler/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" apiVersion: v2 name: teleport-plugin-event-handler diff --git a/examples/chart/event-handler/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/event-handler/tests/__snapshot__/configmap_test.yaml.snap index 9f5583967f3c4..5cb74b7e4ecb3 100644 --- a/examples/chart/event-handler/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/event-handler/tests/__snapshot__/configmap_test.yaml.snap @@ -26,6 +26,6 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-event-handler - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-event-handler-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-event-handler-17.1.2 name: RELEASE-NAME-teleport-plugin-event-handler diff --git a/examples/chart/event-handler/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/event-handler/tests/__snapshot__/deployment_test.yaml.snap index b173ed4735bba..ed9900ba91222 100644 --- a/examples/chart/event-handler/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/event-handler/tests/__snapshot__/deployment_test.yaml.snap @@ -7,8 +7,8 @@ should match the snapshot: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-plugin-event-handler - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-plugin-event-handler-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-plugin-event-handler-17.1.2 name: RELEASE-NAME-teleport-plugin-event-handler spec: replicas: 1 @@ -82,7 +82,7 @@ should mount tls.existingCASecretName and set environment when set in values: value: "true" - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: public.ecr.aws/gravitational/teleport-plugin-event-handler:17.1.1 + image: public.ecr.aws/gravitational/teleport-plugin-event-handler:17.1.2 imagePullPolicy: IfNotPresent name: teleport-plugin-event-handler ports: diff --git a/examples/chart/tbot/Chart.yaml b/examples/chart/tbot/Chart.yaml index 659070fe0bb83..4435abf990d60 100644 --- a/examples/chart/tbot/Chart.yaml +++ b/examples/chart/tbot/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" name: tbot apiVersion: v2 diff --git a/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap index 3b7179aff68ef..db353cabc7134 100644 --- a/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/tbot/tests/__snapshot__/deployment_test.yaml.snap @@ -29,7 +29,7 @@ should match the snapshot (full): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: tbot - helm.sh/chart: tbot-17.1.1 + helm.sh/chart: tbot-17.1.2 test-key: test-label-pod spec: affinity: @@ -68,7 +68,7 @@ should match the snapshot (full): value: "1" - name: TEST_ENV value: test-value - image: public.ecr.aws/gravitational/tbot-distroless:17.1.1 + image: public.ecr.aws/gravitational/tbot-distroless:17.1.2 imagePullPolicy: Always livenessProbe: failureThreshold: 6 @@ -154,7 +154,7 @@ should match the snapshot (simple): app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: tbot - helm.sh/chart: tbot-17.1.1 + helm.sh/chart: tbot-17.1.2 spec: containers: - args: @@ -176,7 +176,7 @@ should match the snapshot (simple): fieldPath: spec.nodeName - name: KUBERNETES_TOKEN_PATH value: /var/run/secrets/tokens/join-sa-token - image: public.ecr.aws/gravitational/tbot-distroless:17.1.1 + image: public.ecr.aws/gravitational/tbot-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 diff --git a/examples/chart/teleport-cluster/Chart.yaml b/examples/chart/teleport-cluster/Chart.yaml index 0821369f96635..537648cfb7138 100644 --- a/examples/chart/teleport-cluster/Chart.yaml +++ b/examples/chart/teleport-cluster/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" name: teleport-cluster apiVersion: v2 diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml index 4a94fd4afb9b5..977e2e6b9792d 100644 --- a/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml +++ b/examples/chart/teleport-cluster/charts/teleport-operator/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" name: teleport-operator apiVersion: v2 diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/auth_clusterrole_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/auth_clusterrole_test.yaml.snap index 8682779208346..7f12c8497ddaf 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/auth_clusterrole_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/auth_clusterrole_test.yaml.snap @@ -8,8 +8,8 @@ adds operator permissions to ClusterRole: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-cluster-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-cluster-17.1.2 teleport.dev/majorVersion: "17" name: RELEASE-NAME rules: diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/auth_config_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/auth_config_test.yaml.snap index b729b1b439d52..e91a45014f311 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/auth_config_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/auth_config_test.yaml.snap @@ -1848,8 +1848,8 @@ sets clusterDomain on Configmap: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-cluster-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-cluster-17.1.2 teleport.dev/majorVersion: "17" name: RELEASE-NAME-auth namespace: NAMESPACE diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap index 4c13d015c4572..240366c3a79d3 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap @@ -8,7 +8,7 @@ - args: - --diag-addr=0.0.0.0:3000 - --apply-on-startup=/etc/teleport/apply-on-startup.yaml - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -141,7 +141,7 @@ should set nodeSelector when set in values: - args: - --diag-addr=0.0.0.0:3000 - --apply-on-startup=/etc/teleport/apply-on-startup.yaml - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -238,7 +238,7 @@ should set resources when set in values: - args: - --diag-addr=0.0.0.0:3000 - --apply-on-startup=/etc/teleport/apply-on-startup.yaml - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -324,7 +324,7 @@ should set securityContext when set in values: - args: - --diag-addr=0.0.0.0:3000 - --apply-on-startup=/etc/teleport/apply-on-startup.yaml - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_config_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_config_test.yaml.snap index 2567287d235b3..720b2144ab303 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_config_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_config_test.yaml.snap @@ -567,8 +567,8 @@ sets clusterDomain on Configmap: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-cluster-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-cluster-17.1.2 teleport.dev/majorVersion: "17" name: RELEASE-NAME-proxy namespace: NAMESPACE diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap index 526abf2578815..23929e5f23810 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap @@ -11,8 +11,8 @@ sets clusterDomain on Deployment Pods: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-cluster-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-cluster-17.1.2 teleport.dev/majorVersion: "17" name: RELEASE-NAME-proxy namespace: NAMESPACE @@ -26,7 +26,7 @@ sets clusterDomain on Deployment Pods: template: metadata: annotations: - checksum/config: f8c36fbfefbeb49c341d941eed325ab6be97bbee82ae23e797b7a6ced382d593 + checksum/config: c573a6b130ea20b773a595c566510c7a5017fa844498efad2fd235f45b2e2311 kubernetes.io/pod: test-annotation kubernetes.io/pod-different: 4 labels: @@ -34,8 +34,8 @@ sets clusterDomain on Deployment Pods: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: teleport-cluster - app.kubernetes.io/version: 17.1.1 - helm.sh/chart: teleport-cluster-17.1.1 + app.kubernetes.io/version: 17.1.2 + helm.sh/chart: teleport-cluster-17.1.2 teleport.dev/majorVersion: "17" spec: affinity: @@ -44,7 +44,7 @@ sets clusterDomain on Deployment Pods: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -105,7 +105,7 @@ sets clusterDomain on Deployment Pods: - wait - no-resolve - RELEASE-NAME-auth-v16.NAMESPACE.svc.test.com - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 name: wait-auth-update serviceAccountName: RELEASE-NAME-proxy terminationGracePeriodSeconds: 60 @@ -137,7 +137,7 @@ should provision initContainer correctly when set in values: - wait - no-resolve - RELEASE-NAME-auth-v16.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 name: wait-auth-update resources: limits: @@ -201,7 +201,7 @@ should set nodeSelector when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -262,7 +262,7 @@ should set nodeSelector when set in values: - wait - no-resolve - RELEASE-NAME-auth-v16.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 name: wait-auth-update nodeSelector: environment: security @@ -313,7 +313,7 @@ should set resources for wait-auth-update initContainer when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -381,7 +381,7 @@ should set resources for wait-auth-update initContainer when set in values: - wait - no-resolve - RELEASE-NAME-auth-v16.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 name: wait-auth-update resources: limits: @@ -421,7 +421,7 @@ should set resources when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -489,7 +489,7 @@ should set resources when set in values: - wait - no-resolve - RELEASE-NAME-auth-v16.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 name: wait-auth-update resources: limits: @@ -529,7 +529,7 @@ should set securityContext for initContainers when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -597,7 +597,7 @@ should set securityContext for initContainers when set in values: - wait - no-resolve - RELEASE-NAME-auth-v16.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 name: wait-auth-update securityContext: allowPrivilegeEscalation: false @@ -637,7 +637,7 @@ should set securityContext when set in values: containers: - args: - --diag-addr=0.0.0.0:3000 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -705,7 +705,7 @@ should set securityContext when set in values: - wait - no-resolve - RELEASE-NAME-auth-v16.NAMESPACE.svc.cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 name: wait-auth-update securityContext: allowPrivilegeEscalation: false diff --git a/examples/chart/teleport-kube-agent/Chart.yaml b/examples/chart/teleport-kube-agent/Chart.yaml index 7385f1f394a80..eedb75037d1d5 100644 --- a/examples/chart/teleport-kube-agent/Chart.yaml +++ b/examples/chart/teleport-kube-agent/Chart.yaml @@ -1,4 +1,4 @@ -.version: &version "17.1.1" +.version: &version "17.1.2" name: teleport-kube-agent apiVersion: v2 diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap index a6582820ca5e8..e2efec0c5c57b 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/deployment_test.yaml.snap @@ -32,7 +32,7 @@ sets Deployment annotations when specified if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -109,7 +109,7 @@ sets Deployment labels when specified if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -173,7 +173,7 @@ sets Pod annotations when specified if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -237,7 +237,7 @@ sets Pod labels when specified if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -322,7 +322,7 @@ should add emptyDir for data when existingDataVolume is not set if action is Upg value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -387,7 +387,7 @@ should add insecureSkipProxyTLSVerify to args when set in values if action is Up value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -451,7 +451,7 @@ should correctly configure existingDataVolume when set if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -513,7 +513,7 @@ should expose diag port if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -589,7 +589,7 @@ should have multiple replicas when replicaCount is set (using .replicaCount, dep value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -665,7 +665,7 @@ should have multiple replicas when replicaCount is set (using highAvailability.r value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -729,7 +729,7 @@ should have one replica when replicaCount is not set if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -793,7 +793,7 @@ should mount extraVolumes and extraVolumeMounts if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -862,7 +862,7 @@ should mount jamfCredentialsSecret if it already exists and when role is jamf an value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -932,7 +932,7 @@ should mount jamfCredentialsSecret.name when role is jamf and action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1004,7 +1004,7 @@ should mount tls.existingCASecretName and set environment when set in values if value: cluster.local - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1078,7 +1078,7 @@ should mount tls.existingCASecretName and set extra environment when set in valu value: http://username:password@my.proxy.host:3128 - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1148,7 +1148,7 @@ should provision initContainer correctly when set in values if action is Upgrade value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1270,7 +1270,7 @@ should set affinity when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1334,7 +1334,7 @@ should set default serviceAccountName when not set in values if action is Upgrad value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1411,7 +1411,7 @@ should set environment when extraEnv set in values if action is Upgrade: value: cluster.local - name: HTTPS_PROXY value: http://username:password@my.proxy.host:3128 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1539,7 +1539,7 @@ should set imagePullPolicy when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: Always livenessProbe: failureThreshold: 6 @@ -1603,7 +1603,7 @@ should set nodeSelector if set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1669,7 +1669,7 @@ should set not set priorityClassName when not set in values if action is Upgrade value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1745,7 +1745,7 @@ should set preferred affinity when more than one replica is used if action is Up value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1809,7 +1809,7 @@ should set priorityClassName when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1874,7 +1874,7 @@ should set probeTimeoutSeconds when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1948,7 +1948,7 @@ should set required affinity when highAvailability.requireAntiAffinity is set if value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2012,7 +2012,7 @@ should set resources when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2083,7 +2083,7 @@ should set serviceAccountName when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2147,7 +2147,7 @@ should set tolerations when set in values if action is Upgrade: value: "true" - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/job_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/job_test.yaml.snap index c51a84a12c1b7..03a6ee1c48405 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/job_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/job_test.yaml.snap @@ -25,7 +25,7 @@ should create ServiceAccount for post-delete hook by default: fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: @@ -108,7 +108,7 @@ should not create ServiceAccount for post-delete hook if serviceAccount.create i fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: @@ -138,7 +138,7 @@ should not create ServiceAccount, Role or RoleBinding for post-delete hook if se fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: @@ -168,7 +168,7 @@ should set nodeSelector in post-delete hook: fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent name: post-delete-job securityContext: @@ -200,7 +200,7 @@ should set resources in the Job's pod spec if resources is set in values: fieldPath: metadata.namespace - name: RELEASE_NAME value: RELEASE-NAME - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent name: post-delete-job resources: diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap index 7ebf952de87bb..26808dae97bac 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/statefulset_test.yaml.snap @@ -18,7 +18,7 @@ sets Pod annotations when specified: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -91,7 +91,7 @@ sets Pod labels when specified: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -188,7 +188,7 @@ sets StatefulSet labels when specified: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -293,7 +293,7 @@ should add insecureSkipProxyTLSVerify to args when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -366,7 +366,7 @@ should add volumeClaimTemplate for data volume when using StatefulSet and action value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -459,7 +459,7 @@ should add volumeClaimTemplate for data volume when using StatefulSet and is Fre value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -542,7 +542,7 @@ should add volumeMount for data volume when using StatefulSet: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -615,7 +615,7 @@ should expose diag port: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -688,7 +688,7 @@ should generate Statefulset when storage is disabled and mode is a Upgrade: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -775,7 +775,7 @@ should have multiple replicas when replicaCount is set (using .replicaCount, dep value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -860,7 +860,7 @@ should have multiple replicas when replicaCount is set (using highAvailability.r value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -933,7 +933,7 @@ should have one replica when replicaCount is not set: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1006,7 +1006,7 @@ should install Statefulset when storage is disabled and mode is a Fresh Install: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1081,7 +1081,7 @@ should mount extraVolumes and extraVolumeMounts: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1159,7 +1159,7 @@ should mount jamfCredentialsSecret if it already exists and when role is jamf: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1240,7 +1240,7 @@ should mount jamfCredentialsSecret.name when role is jamf: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1323,7 +1323,7 @@ should mount tls.existingCASecretName and set environment when set in values: value: cluster.local - name: SSL_CERT_FILE value: /etc/teleport-tls-ca/ca.pem - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1408,7 +1408,7 @@ should mount tls.existingCASecretName and set extra environment when set in valu value: /etc/teleport-tls-ca/ca.pem - name: HTTPS_PROXY value: http://username:password@my.proxy.host:3128 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1489,7 +1489,7 @@ should not add emptyDir for data when using StatefulSet: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1562,7 +1562,7 @@ should provision initContainer correctly when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1693,7 +1693,7 @@ should set affinity when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1766,7 +1766,7 @@ should set default serviceAccountName when not set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1852,7 +1852,7 @@ should set environment when extraEnv set in values: value: cluster.local - name: HTTPS_PROXY value: http://username:password@my.proxy.host:3128 - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -1998,7 +1998,7 @@ should set imagePullPolicy when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: Always livenessProbe: failureThreshold: 6 @@ -2071,7 +2071,7 @@ should set nodeSelector if set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2158,7 +2158,7 @@ should set preferred affinity when more than one replica is used: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2231,7 +2231,7 @@ should set probeTimeoutSeconds when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2314,7 +2314,7 @@ should set required affinity when highAvailability.requireAntiAffinity is set: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2387,7 +2387,7 @@ should set resources when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2467,7 +2467,7 @@ should set serviceAccountName when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2540,7 +2540,7 @@ should set storage.requests when set in values and action is an Upgrade: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2613,7 +2613,7 @@ should set storage.storageClassName when set in values and action is an Upgrade: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -2686,7 +2686,7 @@ should set tolerations when set in values: value: RELEASE-NAME - name: TELEPORT_KUBE_CLUSTER_DOMAIN value: cluster.local - image: public.ecr.aws/gravitational/teleport-distroless:17.1.1 + image: public.ecr.aws/gravitational/teleport-distroless:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 diff --git a/examples/chart/teleport-kube-agent/tests/__snapshot__/updater_deployment_test.yaml.snap b/examples/chart/teleport-kube-agent/tests/__snapshot__/updater_deployment_test.yaml.snap index ecf0d0aa8c317..c196e173b9ec5 100644 --- a/examples/chart/teleport-kube-agent/tests/__snapshot__/updater_deployment_test.yaml.snap +++ b/examples/chart/teleport-kube-agent/tests/__snapshot__/updater_deployment_test.yaml.snap @@ -27,7 +27,7 @@ sets the affinity: - --base-image=public.ecr.aws/gravitational/teleport-distroless - --version-server=https://my-custom-version-server/v1 - --version-channel=custom/preview - image: public.ecr.aws/gravitational/teleport-kube-agent-updater:17.1.1 + image: public.ecr.aws/gravitational/teleport-kube-agent-updater:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 @@ -73,7 +73,7 @@ sets the tolerations: - --base-image=public.ecr.aws/gravitational/teleport-distroless - --version-server=https://my-custom-version-server/v1 - --version-channel=custom/preview - image: public.ecr.aws/gravitational/teleport-kube-agent-updater:17.1.1 + image: public.ecr.aws/gravitational/teleport-kube-agent-updater:17.1.2 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 6 From defaf5c6912c9fa51c17312e1acf44b691bb50e6 Mon Sep 17 00:00:00 2001 From: matheus Date: Mon, 30 Dec 2024 14:04:54 -0300 Subject: [PATCH 15/30] Add scheduled upgrades warning to support page (#49404) (#50499) --- web/packages/teleport/src/Support/Support.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/web/packages/teleport/src/Support/Support.tsx b/web/packages/teleport/src/Support/Support.tsx index 59012984cb941..7bff9ae9e7ec9 100644 --- a/web/packages/teleport/src/Support/Support.tsx +++ b/web/packages/teleport/src/Support/Support.tsx @@ -96,6 +96,27 @@ export const Support = ({ {isEnterprise && !cfg.isCloud && licenseExpiryDateText && ( )} + {isCloud && ( + + + + Looking for{' '} + + Scheduled Upgrades? + {' '} + It is now in{' '} + + Cluster Management + {' '} + page. + + + )} From 8331d852c1ac5f729597c30e66417cacc5a1b9d5 Mon Sep 17 00:00:00 2001 From: Maxim Dietz Date: Mon, 30 Dec 2024 13:10:54 -0400 Subject: [PATCH 16/30] [v17] Tidy reused Menu components (#50523) * chore: Tidy reused Menu components * Add generic MultiselectMenu, ViewModeSwitch, SortMenu components * test: Add Stories for new control components --- .../Controls/MultiselectMenu.story.tsx | 137 ++++++ .../components/Controls/MultiselectMenu.tsx | 243 +++++++++++ .../components/Controls/SortMenu.story.tsx | 83 ++++ .../shared/components/Controls/SortMenu.tsx | 120 ++++++ .../Controls/ViewModeSwitch.story.tsx | 61 +++ .../components/Controls/ViewModeSwitch.tsx | 120 ++++++ .../UnifiedResources/FilterPanel.tsx | 392 ++---------------- 7 files changed, 801 insertions(+), 355 deletions(-) create mode 100644 web/packages/shared/components/Controls/MultiselectMenu.story.tsx create mode 100644 web/packages/shared/components/Controls/MultiselectMenu.tsx create mode 100644 web/packages/shared/components/Controls/SortMenu.story.tsx create mode 100644 web/packages/shared/components/Controls/SortMenu.tsx create mode 100644 web/packages/shared/components/Controls/ViewModeSwitch.story.tsx create mode 100644 web/packages/shared/components/Controls/ViewModeSwitch.tsx diff --git a/web/packages/shared/components/Controls/MultiselectMenu.story.tsx b/web/packages/shared/components/Controls/MultiselectMenu.story.tsx new file mode 100644 index 0000000000000..3016d892c64a5 --- /dev/null +++ b/web/packages/shared/components/Controls/MultiselectMenu.story.tsx @@ -0,0 +1,137 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { useState } from 'react'; +import { Flex } from 'design'; + +import { MultiselectMenu } from './MultiselectMenu'; + +import type { Meta, StoryFn, StoryObj } from '@storybook/react'; + +type OptionValue = `option-${number}`; + +const options: { + value: OptionValue; + label: string | React.ReactNode; + disabled?: boolean; + disabledTooltip?: string; +}[] = [ + { value: 'option-1', label: 'Option 1' }, + { value: 'option-2', label: 'Option 2' }, + { value: 'option-3', label: 'Option 3' }, + { value: 'option-4', label: 'Option 4' }, +]; + +const optionsWithCustomLabels: typeof options = [ + { + value: 'option-1', + label: Bold Option 1, + }, + { + value: 'option-3', + label: Italic Option 3, + }, +]; + +export default { + title: 'Shared/Controls/MultiselectMenu', + component: MultiselectMenu, + argTypes: { + buffered: { + control: { type: 'boolean' }, + description: 'Buffer selections until "Apply" is clicked', + table: { defaultValue: { summary: 'false' } }, + }, + showIndicator: { + control: { type: 'boolean' }, + description: 'Show indicator when there are selected options', + table: { defaultValue: { summary: 'true' } }, + }, + showSelectControls: { + control: { type: 'boolean' }, + description: 'Show select controls (Select All/Select None)', + table: { defaultValue: { summary: 'true' } }, + }, + label: { + control: { type: 'text' }, + description: 'Label for the multiselect', + }, + tooltip: { + control: { type: 'text' }, + description: 'Tooltip for the label', + }, + selected: { + control: false, + description: 'Currently selected options', + table: { type: { summary: 'T[]' } }, + }, + onChange: { + control: false, + description: 'Callback when selection changes', + table: { type: { summary: 'selected: T[]' } }, + }, + options: { + control: false, + description: 'Options to select from', + table: { + type: { + summary: + 'Array<{ value: T; label: string | ReactNode; disabled?: boolean; disabledTooltip?: string; }>', + }, + }, + }, + }, + args: { + label: 'Select Options', + tooltip: 'Choose multiple options', + buffered: false, + showIndicator: true, + showSelectControls: true, + }, + parameters: { controls: { expanded: true, exclude: ['userContext'] } }, + render: (args => { + const [selected, setSelected] = useState([]); + return ( + + + + ); + }) satisfies StoryFn>, +} satisfies Meta>; + +type Story = StoryObj>; + +const Default: Story = { args: { options } }; + +const WithCustomLabels: Story = { args: { options: optionsWithCustomLabels } }; + +const WithDisabledOption: Story = { + args: { + options: [ + ...options, + { + value: 'option-5', + label: 'Option 5', + disabled: true, + disabledTooltip: 'Lorum ipsum dolor sit amet', + }, + ], + }, +}; + +export { Default, WithCustomLabels, WithDisabledOption }; diff --git a/web/packages/shared/components/Controls/MultiselectMenu.tsx b/web/packages/shared/components/Controls/MultiselectMenu.tsx new file mode 100644 index 0000000000000..f252cf7aa21be --- /dev/null +++ b/web/packages/shared/components/Controls/MultiselectMenu.tsx @@ -0,0 +1,243 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { ReactNode, useState } from 'react'; +import styled from 'styled-components'; +import { + ButtonPrimary, + ButtonSecondary, + Flex, + Menu, + MenuItem, + Text, +} from 'design'; +import { ChevronDown } from 'design/Icon'; +import { CheckboxInput } from 'design/Checkbox'; + +import { HoverTooltip } from 'shared/components/ToolTip'; + +type MultiselectMenuProps = { + options: { + value: T; + label: string | ReactNode; + disabled?: boolean; + disabledTooltip?: string; + }[]; + selected: T[]; + onChange: (selected: T[]) => void; + label: string | ReactNode; + tooltip: string; + buffered?: boolean; + showIndicator?: boolean; + showSelectControls?: boolean; +}; + +export const MultiselectMenu = ({ + onChange, + options, + selected, + label, + tooltip, + buffered = false, + showIndicator = true, + showSelectControls = true, +}: MultiselectMenuProps) => { + // we have a separate state in the filter so we can select a few different things and then click "apply" + const [intSelected, setIntSelected] = useState([]); + const [anchorEl, setAnchorEl] = useState(null); + const handleOpen = ( + event: React.MouseEvent + ) => { + setIntSelected(selected || []); + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + // if we cancel, we reset the options to what is already selected in the params + const cancelUpdate = () => { + setIntSelected(selected || []); + handleClose(); + }; + + const handleSelect = (value: T) => { + let newSelected = (buffered ? intSelected : selected).slice(); + + if (newSelected.includes(value)) { + newSelected = newSelected.filter(v => v !== value); + } else { + newSelected.push(value); + } + + (buffered ? setIntSelected : onChange)(newSelected); + }; + + const handleSelectAll = () => { + (buffered ? setIntSelected : onChange)( + options.filter(o => !o.disabled).map(o => o.value) + ); + }; + + const handleClearAll = () => { + (buffered ? setIntSelected : onChange)([]); + }; + + const applyFilters = () => { + onChange(intSelected); + handleClose(); + }; + + return ( + + + + {label} {selected?.length > 0 ? `(${selected?.length})` : ''} + + {selected?.length > 0 && showIndicator && } + + + `margin-top: 36px;`} + menuListCss={() => `overflow-y: auto;`} + transformOrigin={{ + vertical: 'top', + horizontal: 'left', + }} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }} + anchorEl={anchorEl} + open={Boolean(anchorEl)} + onClose={cancelUpdate} + > + {showSelectControls && ( + + + Select All + + + Clear All + + + )} + {options.map(opt => { + const $checkbox = ( + <> + { + handleSelect(opt.value); + }} + id={opt.value} + checked={(buffered ? intSelected : selected)?.includes( + opt.value + )} + /> + + {opt.label} + + + ); + return ( + (!opt.disabled ? handleSelect(opt.value) : null)} + > + {opt.disabled && opt.disabledTooltip ? ( + + {$checkbox} + + ) : ( + $checkbox + )} + + ); + })} + {buffered && ( + + + Apply Filters + + + Cancel + + + )} + + + ); +}; + +const MultiselectMenuOptionsContainer = styled(Flex)<{ + position: 'top' | 'bottom'; +}>` + position: sticky; + ${p => (p.position === 'top' ? 'top: 0;' : 'bottom: 0;')} + background-color: ${p => p.theme.colors.levels.elevated}; + z-index: 1; +`; + +const FiltersExistIndicator = styled.div` + position: absolute; + top: -4px; + right: -4px; + height: 12px; + width: 12px; + background-color: ${p => p.theme.colors.brand}; + border-radius: 50%; + display: inline-block; +`; diff --git a/web/packages/shared/components/Controls/SortMenu.story.tsx b/web/packages/shared/components/Controls/SortMenu.story.tsx new file mode 100644 index 0000000000000..56158b8a8d228 --- /dev/null +++ b/web/packages/shared/components/Controls/SortMenu.story.tsx @@ -0,0 +1,83 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { useState } from 'react'; +import { Flex } from 'design'; + +import { SortMenu } from './SortMenu'; + +import type { Meta, StoryFn, StoryObj } from '@storybook/react'; + +export default { + title: 'Shared/Controls/SortMenu', + component: SortMenu, + argTypes: { + current: { + control: false, + description: 'Current sort', + table: { + type: { + summary: + "Array<{ fieldName: Exclude; dir: 'ASC' | 'DESC'>", + }, + }, + }, + fields: { + control: false, + description: 'Fields to sort by', + table: { + type: { + summary: + '{ value: Exclude; label: string }[]', + }, + }, + }, + onChange: { + control: false, + description: 'Callback when fieldName or dir is changed', + table: { + type: { + summary: + "(value: { fieldName: Exclude; dir: 'ASC' | 'DESC' }) => void", + }, + }, + }, + }, + args: { + current: { fieldName: 'name', dir: 'ASC' }, + fields: [ + { value: 'name', label: 'Name' }, + { value: 'created', label: 'Created' }, + { value: 'updated', label: 'Updated' }, + ], + }, + parameters: { controls: { expanded: true, exclude: ['userContext'] } }, +} satisfies Meta>; + +const Default: StoryObj = { + render: (({ current, fields }) => { + const [sort, setSort] = useState(current); + return ( + + + + ); + }) satisfies StoryFn, +}; + +export { Default as SortMenu }; diff --git a/web/packages/shared/components/Controls/SortMenu.tsx b/web/packages/shared/components/Controls/SortMenu.tsx new file mode 100644 index 0000000000000..d6bbc5cdf0d2d --- /dev/null +++ b/web/packages/shared/components/Controls/SortMenu.tsx @@ -0,0 +1,120 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { useState } from 'react'; +import { ButtonBorder, Flex, Menu, MenuItem } from 'design'; +import { ArrowDown, ArrowUp } from 'design/Icon'; + +import { HoverTooltip } from 'shared/components/ToolTip'; + +type SortMenuSort = { + fieldName: Exclude; + dir: 'ASC' | 'DESC'; +}; + +export const SortMenu = ({ + current, + fields, + onChange, +}: { + current: SortMenuSort; + fields: { value: SortMenuSort['fieldName']; label: string }[]; + onChange: (value: SortMenuSort) => void; +}) => { + const [anchorEl, setAnchorEl] = useState(null); + + const handleOpen = (event: React.MouseEvent) => + setAnchorEl(event.currentTarget); + + const handleClose = () => setAnchorEl(null); + + const handleSelect = (value: (typeof fields)[number]['value']) => { + handleClose(); + onChange({ + fieldName: value, + dir: current.dir, + }); + }; + + return ( + + + props.theme.colors.spotBackground[2]}; + `} + textTransform="none" + size="small" + px={2} + onClick={handleOpen} + aria-label="Sort by" + aria-haspopup="true" + aria-expanded={!!anchorEl} + > + {fields.find(f => f.value === current.fieldName)?.label} + + + `margin-top: 36px; margin-left: 28px;`} + transformOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'right', + }} + anchorEl={anchorEl} + open={Boolean(anchorEl)} + onClose={handleClose} + > + {fields.map(({ value, label }) => ( + handleSelect(value)}> + {label} + + ))} + + + + onChange({ + fieldName: current.fieldName, + dir: current.dir === 'ASC' ? 'DESC' : 'ASC', + }) + } + textTransform="none" + css={` + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-color: ${props => props.theme.colors.spotBackground[2]}; + `} + size="small" + > + {current.dir === 'ASC' ? ( + + ) : ( + + )} + + + + ); +}; diff --git a/web/packages/shared/components/Controls/ViewModeSwitch.story.tsx b/web/packages/shared/components/Controls/ViewModeSwitch.story.tsx new file mode 100644 index 0000000000000..adc77e1975715 --- /dev/null +++ b/web/packages/shared/components/Controls/ViewModeSwitch.story.tsx @@ -0,0 +1,61 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { useState } from 'react'; +import { Flex } from 'design'; + +import { ViewMode } from 'gen-proto-ts/teleport/userpreferences/v1/unified_resource_preferences_pb'; + +import { ViewModeSwitch } from './ViewModeSwitch'; + +import type { Meta, StoryFn, StoryObj } from '@storybook/react'; + +export default { + title: 'Shared/Controls/ViewModeSwitch', + component: ViewModeSwitch, + argTypes: { + currentViewMode: { + control: { type: 'radio', options: [ViewMode.CARD, ViewMode.LIST] }, + description: 'Current view mode', + table: { defaultValue: { summary: ViewMode.CARD.toString() } }, + }, + setCurrentViewMode: { + control: false, + description: 'Callback to set current view mode', + table: { type: { summary: '(newViewMode: ViewMode) => void' } }, + }, + }, + args: { currentViewMode: ViewMode.CARD }, + parameters: { controls: { expanded: true, exclude: ['userContext'] } }, +} satisfies Meta; + +const Default: StoryObj = { + render: (({ currentViewMode }) => { + const [viewMode, setViewMode] = useState(currentViewMode); + return ( + + + + ); + }) satisfies StoryFn, +}; + +export { Default as ViewModeSwitch }; diff --git a/web/packages/shared/components/Controls/ViewModeSwitch.tsx b/web/packages/shared/components/Controls/ViewModeSwitch.tsx new file mode 100644 index 0000000000000..7997f2de29f66 --- /dev/null +++ b/web/packages/shared/components/Controls/ViewModeSwitch.tsx @@ -0,0 +1,120 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React from 'react'; +import styled from 'styled-components'; +import { Rows, SquaresFour } from 'design/Icon'; + +import { ViewMode } from 'gen-proto-ts/teleport/userpreferences/v1/unified_resource_preferences_pb'; + +import { HoverTooltip } from 'shared/components/ToolTip'; + +export const ViewModeSwitch = ({ + currentViewMode, + setCurrentViewMode, +}: { + currentViewMode: ViewMode; + setCurrentViewMode: (viewMode: ViewMode) => void; +}) => { + return ( + + + setCurrentViewMode(ViewMode.CARD)} + role="radio" + aria-label="Card View" + aria-checked={currentViewMode === ViewMode.CARD} + first + > + + + + + setCurrentViewMode(ViewMode.LIST)} + role="radio" + aria-label="List View" + aria-checked={currentViewMode === ViewMode.LIST} + last + > + + + + + ); +}; + +const ViewModeSwitchContainer = styled.div` + height: 22px; + border: ${p => p.theme.borders[1]} ${p => p.theme.colors.spotBackground[2]}; + border-radius: ${p => p.theme.radii[2]}px; + display: flex; + + .selected { + background-color: ${p => p.theme.colors.spotBackground[1]}; + + &:focus-visible, + &:hover { + background-color: ${p => p.theme.colors.spotBackground[1]}; + } + } +`; + +const ViewModeSwitchButton = styled.button<{ first?: boolean; last?: boolean }>` + height: 100%; + width: 100%; + overflow: hidden; + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + background-color: transparent; + outline: none; + transition: outline-width 150ms ease; + + ${p => + p.first && + ` + border-top-left-radius: ${p.theme.radii[2]}px; + border-bottom-left-radius: ${p.theme.radii[2]}px; + border-right: ${p.theme.borders[1]} ${p.theme.colors.spotBackground[2]}; + `} + ${p => + p.last && + ` + border-top-right-radius: ${p.theme.radii[2]}px; + border-bottom-right-radius: ${p.theme.radii[2]}px; + `} + + &:focus-visible { + outline: ${p => p.theme.borders[1]} + ${p => p.theme.colors.text.slightlyMuted}; + } + + &:focus-visible, + &:hover { + background-color: ${p => p.theme.colors.spotBackground[0]}; + } +`; diff --git a/web/packages/shared/components/UnifiedResources/FilterPanel.tsx b/web/packages/shared/components/UnifiedResources/FilterPanel.tsx index eb3763d7a5ae7..6e62a50a3d4c9 100644 --- a/web/packages/shared/components/UnifiedResources/FilterPanel.tsx +++ b/web/packages/shared/components/UnifiedResources/FilterPanel.tsx @@ -18,25 +18,19 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import { ButtonBorder, ButtonPrimary, ButtonSecondary } from 'design/Button'; -import { SortDir } from 'design/DataTable/types'; +import { ButtonBorder, ButtonSecondary } from 'design/Button'; import { Text, Flex, Toggle } from 'design'; -import Menu, { MenuItem } from 'design/Menu'; +import Menu from 'design/Menu'; import { CheckboxInput } from 'design/Checkbox'; -import { - ArrowUp, - ArrowDown, - ChevronDown, - SquaresFour, - Rows, - ArrowsIn, - ArrowsOut, - Refresh, -} from 'design/Icon'; +import { ChevronDown, ArrowsIn, ArrowsOut, Refresh } from 'design/Icon'; import { ViewMode } from 'gen-proto-ts/teleport/userpreferences/v1/unified_resource_preferences_pb'; -import { HoverTooltip } from 'design/Tooltip'; +import { HoverTooltip } from 'shared/components/ToolTip'; +import { SortMenu } from 'shared/components/Controls/SortMenu'; +import { ViewModeSwitch } from 'shared/components/Controls/ViewModeSwitch'; + +import { MultiselectMenu } from 'shared/components/Controls/MultiselectMenu'; import { ResourceAvailabilityFilter, FilterKind } from './UnifiedResources'; import { @@ -136,11 +130,17 @@ export function FilterPanel({ data-testid="select_all" /> - - ({ + value: kind, + label: kindToLabel[kind], + disabled: disabled, + }))} + selected={kinds || []} onChange={onKindsChanged} - availableKinds={availableKinds} - kindsFromParams={kinds || []} + label="Types" + tooltip="Filter by resource type" + buffered /> {ClusterDropdown} {availabilityFilter && ( @@ -197,10 +197,19 @@ export function FilterPanel({ )} { + if (newSort.dir !== sort.dir) { + onSortOrderButtonClicked(); + } + if (newSort.fieldName !== activeSortFieldOption.value) { + onSortFieldChange(newSort.fieldName); + } + }} /> @@ -221,303 +230,6 @@ function oppositeSort( } } -type FilterTypesMenuProps = { - availableKinds: FilterKind[]; - kindsFromParams: string[]; - onChange: (kinds: string[]) => void; -}; - -const FilterTypesMenu = ({ - onChange, - availableKinds, - kindsFromParams, -}: FilterTypesMenuProps) => { - const kindOptions = availableKinds.map(({ kind, disabled }) => ({ - value: kind, - label: kindToLabel[kind], - disabled: disabled, - })); - - const [anchorEl, setAnchorEl] = useState(null); - // we have a separate state in the filter so we can select a few different things and then click "apply" - const [kinds, setKinds] = useState(kindsFromParams || []); - const handleOpen = event => { - setKinds(kindsFromParams); - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - // if we cancel, we reset the kinds to what is already selected in the params - const cancelUpdate = () => { - setKinds(kindsFromParams); - handleClose(); - }; - - const handleSelect = (value: string) => { - let newKinds = [...kinds]; - if (newKinds.includes(value)) { - newKinds = newKinds.filter(v => v !== value); - } else { - newKinds.push(value); - } - setKinds(newKinds); - }; - - const handleSelectAll = () => { - setKinds(kindOptions.filter(k => !k.disabled).map(k => k.value)); - }; - - const handleClearAll = () => { - setKinds([]); - }; - - const applyFilters = () => { - onChange(kinds); - handleClose(); - }; - - return ( - - - - Types{' '} - {kindsFromParams.length > 0 ? `(${kindsFromParams.length})` : ''} - - {kindsFromParams.length > 0 && } - - - `margin-top: 36px;`} - transformOrigin={{ - vertical: 'top', - horizontal: 'left', - }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - anchorEl={anchorEl} - open={Boolean(anchorEl)} - onClose={cancelUpdate} - > - - - Select All - - - Clear All - - - {kindOptions.map(kind => { - const $checkbox = ( - <> - { - handleSelect(kind.value); - }} - id={kind.value} - checked={kinds.includes(kind.value)} - /> - - {kind.label} - - - ); - return ( - (!kind.disabled ? handleSelect(kind.value) : null)} - > - {kind.disabled ? ( - - {$checkbox} - - ) : ( - $checkbox - )} - - ); - })} - - - Apply Filters - - - Cancel - - - - - ); -}; - -type SortMenuProps = { - transformOrigin?: any; - anchorOrigin?: any; - sortType: string; - sortDir: SortDir; - onChange: (value: string) => void; - onDirChange: () => void; -}; - -const SortMenu: React.FC = props => { - const { sortType, onChange, onDirChange, sortDir } = props; - const [anchorEl, setAnchorEl] = React.useState(null); - - const handleOpen = event => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const handleSelect = (value: string) => { - handleClose(); - onChange(value); - }; - - return ( - - - props.theme.colors.spotBackground[2]}; - `} - textTransform="none" - size="small" - px={2} - onClick={handleOpen} - > - {sortType} - - - `margin-top: 36px;`} - transformOrigin={{ - vertical: 'top', - horizontal: 'center', - }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'center', - }} - anchorEl={anchorEl} - open={Boolean(anchorEl)} - onClose={handleClose} - > - handleSelect('name')}>Name - handleSelect('kind')}>Type - - - props.theme.colors.spotBackground[2]}; - `} - size="small" - > - {sortDir === 'ASC' ? : } - - - - ); -}; - -function kindArraysEqual(arr1: string[], arr2: string[]) { - if (arr1.length !== arr2.length) { - return false; - } - - const sortedArr1 = arr1.slice().sort(); - const sortedArr2 = arr2.slice().sort(); - - for (let i = 0; i < sortedArr1.length; i++) { - if (sortedArr1[i] !== sortedArr2[i]) { - return false; - } - } - - return true; -} - -function ViewModeSwitch({ - currentViewMode, - setCurrentViewMode, -}: { - currentViewMode: ViewMode; - setCurrentViewMode: (viewMode: ViewMode) => void; -}) { - return ( - - setCurrentViewMode(ViewMode.CARD)} - css={` - border-right: 1px solid - ${props => props.theme.colors.spotBackground[2]}; - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - `} - > - - - setCurrentViewMode(ViewMode.LIST)} - css={` - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - `} - > - - - - ); -} - const IncludedResourcesSelector = ({ onChange, availabilityFilter, @@ -580,7 +292,9 @@ const IncludedResourcesSelector = ({ onClose={handleClose} > - Show requestable resources + + Show requestable resources + props.theme.colors.spotBackground[2]}; - border-radius: 4px; - display: flex; - - .selected { - background-color: ${props => props.theme.colors.spotBackground[1]}; - - &:hover { - background-color: ${props => props.theme.colors.spotBackground[1]}; - } - } -`; - -const ViewModeSwitchButton = styled.button` - height: 100%; - width: 50%; - overflow: hidden; - border: none; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - - background-color: transparent; - - &:hover { - background-color: ${props => props.theme.colors.spotBackground[0]}; - } -`; - const FiltersExistIndicator = styled.div` position: absolute; top: -4px; @@ -641,8 +322,9 @@ const FiltersExistIndicator = styled.div` const AccessRequestsToggleItem = styled.div` min-height: 40px; box-sizing: border-box; - padding-left: ${props => props.theme.space[2]}px; - padding-right: ${props => props.theme.space[2]}px; + padding-top: 2px; + padding-left: ${props => props.theme.space[3]}px; + padding-right: ${props => props.theme.space[3]}px; display: flex; justify-content: flex-start; align-items: center; From 2ffe7ec4abe64c6d57f06a55bcaa4cb715e1d379 Mon Sep 17 00:00:00 2001 From: Maxim Dietz Date: Mon, 30 Dec 2024 13:16:32 -0400 Subject: [PATCH 17/30] feat: Update Slack integration to link directly to ACL review state (#50560) --- integrations/access/slack/bot.go | 1 + 1 file changed, 1 insertion(+) diff --git a/integrations/access/slack/bot.go b/integrations/access/slack/bot.go index dff6c17bbad27..9c58093cb9897 100644 --- a/integrations/access/slack/bot.go +++ b/integrations/access/slack/bot.go @@ -291,6 +291,7 @@ func (b Bot) slackAccessListReminderMsgSection(accessList *accesslist.AccessList if b.webProxyURL != nil { reqURL := *b.webProxyURL reqURL.Path = lib.BuildURLPath("web", "accesslists", accessList.Metadata.Name) + reqURL.Fragment = "review" link = fmt.Sprintf("*Link*: %s", reqURL.String()) } From e381ef7753d6be8f43bbe4a2926e1330abe1972f Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Mon, 30 Dec 2024 11:14:52 -0700 Subject: [PATCH 18/30] Improve error message when RDP connection times out (#50616) --- lib/srv/desktop/rdp/rdpclient/src/lib.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/srv/desktop/rdp/rdpclient/src/lib.rs b/lib/srv/desktop/rdp/rdpclient/src/lib.rs index 200f1b6a6186a..c82663f704c73 100644 --- a/lib/srv/desktop/rdp/rdpclient/src/lib.rs +++ b/lib/srv/desktop/rdp/rdpclient/src/lib.rs @@ -39,6 +39,7 @@ use rdpdr::tdp::{ }; use std::ffi::CString; use std::fmt::Debug; +use std::io::ErrorKind; use std::os::raw::c_char; use std::ptr; use util::{from_c_string, from_go_array}; @@ -141,12 +142,17 @@ pub unsafe extern "C" fn client_run(cgo_handle: CgoHandle, params: CGOConnectPar None => ptr::null_mut(), }, }, - Err(e) => { error!("client_run failed: {:?}", e); + let message = match e { + client::ClientError::Tcp(io_err) if io_err.kind() == ErrorKind::TimedOut => { + String::from(TIMEOUT_ERROR_MESSAGE) + } + _ => format!("{}", e), + }; CGOResult { err_code: CGOErrCode::ErrCodeFailure, - message: CString::new(format!("{}", e)) + message: CString::new(message) .map(|c| c.into_raw()) .unwrap_or(ptr::null_mut()), } @@ -154,6 +160,13 @@ pub unsafe extern "C" fn client_run(cgo_handle: CgoHandle, params: CGOConnectPar } } +const TIMEOUT_ERROR_MESSAGE: &str = "Connection Timed Out\n\n\ +Teleport could not connect to the host within the timeout period. \ +This could be due to a firewall blocking connections, an overloaded system, \ +or network congestion. To resolve this issue, ensure that the Teleport agent \ +has connectivity to the Windows host.\n\n\ +Use \"nc -vz HOST 3389\" to help debug this issue."; + fn handle_operation(cgo_handle: CgoHandle, ctx: &'static str, f: T) -> CGOErrCode where T: FnOnce(ClientHandle) -> ClientResult<()>, From 05bf01590d722b7a83f7b2fae98afab7eef65d6f Mon Sep 17 00:00:00 2001 From: Lisa Kim Date: Mon, 30 Dec 2024 11:07:31 -0800 Subject: [PATCH 19/30] [v17] Web: Add MarkInverse and ResourceLabelTooltip contents (#50572) * Add a inverse Mark version inside tooltips * Add tooltip contents for resource labels * Fix spacing for ButtonTextWithAddIcon * Add index file for export * Rename MarkForTooltip to MarkInverse * Address CRs --- web/packages/design/src/Mark/Mark.story.tsx | 12 +- web/packages/design/src/Mark/Mark.tsx | 13 ++ web/packages/design/src/Mark/index.ts | 2 +- .../design/src/theme/themes/bblpTheme.ts | 1 + .../design/src/theme/themes/darkTheme.ts | 1 + .../design/src/theme/themes/lightTheme.ts | 1 + web/packages/design/src/theme/themes/types.ts | 1 + .../ButtonTextWithAddIcon.tsx | 2 +- .../ResourceLabelTooltip.story.tsx | 29 ++++ .../ResourceLabelTooltip.tsx | 147 ++++++++++++++++++ .../Shared/ResourceLabelTooltip/index.ts | 19 +++ 11 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/ResourceLabelTooltip.story.tsx create mode 100644 web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/ResourceLabelTooltip.tsx create mode 100644 web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/index.ts diff --git a/web/packages/design/src/Mark/Mark.story.tsx b/web/packages/design/src/Mark/Mark.story.tsx index fdc202a8f2044..0b532cd03d2ab 100644 --- a/web/packages/design/src/Mark/Mark.story.tsx +++ b/web/packages/design/src/Mark/Mark.story.tsx @@ -17,8 +17,9 @@ */ import Box from 'design/Box'; +import { IconTooltip } from 'design/Tooltip'; -import { Mark as M } from './Mark'; +import { Mark as M, MarkInverse } from './Mark'; export default { title: 'Design/Mark', @@ -41,3 +42,12 @@ export const SampleText = () => { ); }; + +export const MarkInsideTooltip = () => { + return ( + + Example of MarkInverse component. Note the{' '} + inversed background and text color. + + ); +}; diff --git a/web/packages/design/src/Mark/Mark.tsx b/web/packages/design/src/Mark/Mark.tsx index 2c76a92e0a25c..95552b8e9882b 100644 --- a/web/packages/design/src/Mark/Mark.tsx +++ b/web/packages/design/src/Mark/Mark.tsx @@ -26,3 +26,16 @@ export const Mark = styled.mark` background-color: ${p => p.theme.colors.interactive.tonal.neutral[2]}; color: inherit; `; + +/** + * Returns a MarkInverse that inverts the colors from its parent Mark. + * For example, if current theme is dark theme, parent Mark would use + * light colors, but MarkInverse will use dark colors. + * + * Intended for use in tooltips since tooltips uses inverse background + * color of the current theme. + */ +export const MarkInverse = styled(Mark)` + background-color: ${p => p.theme.colors.tooltip.inverseBackground}; + color: ${p => p.theme.colors.text.main}; +`; diff --git a/web/packages/design/src/Mark/index.ts b/web/packages/design/src/Mark/index.ts index f12d3f30412e7..8ff108e93731d 100644 --- a/web/packages/design/src/Mark/index.ts +++ b/web/packages/design/src/Mark/index.ts @@ -16,4 +16,4 @@ * along with this program. If not, see . */ -export { Mark } from './Mark'; +export { Mark, MarkInverse } from './Mark'; diff --git a/web/packages/design/src/theme/themes/bblpTheme.ts b/web/packages/design/src/theme/themes/bblpTheme.ts index 94c03f70948b8..bab7835d0c80d 100644 --- a/web/packages/design/src/theme/themes/bblpTheme.ts +++ b/web/packages/design/src/theme/themes/bblpTheme.ts @@ -204,6 +204,7 @@ const colors: ThemeColors = { tooltip: { background: 'rgba(255, 255, 255, 0.8)', + inverseBackground: 'rgba(0, 0, 0, 0.5)', }, progressBarColor: '#00BFA5', diff --git a/web/packages/design/src/theme/themes/darkTheme.ts b/web/packages/design/src/theme/themes/darkTheme.ts index 9ba4d7b2a98c4..1a7ce32ece88c 100644 --- a/web/packages/design/src/theme/themes/darkTheme.ts +++ b/web/packages/design/src/theme/themes/darkTheme.ts @@ -203,6 +203,7 @@ const colors: ThemeColors = { tooltip: { background: 'rgba(255, 255, 255, 0.8)', + inverseBackground: 'rgba(0, 0, 0, 0.5)', }, progressBarColor: '#00BFA5', diff --git a/web/packages/design/src/theme/themes/lightTheme.ts b/web/packages/design/src/theme/themes/lightTheme.ts index 8c2cc6daf7677..c19ebb8e991cc 100644 --- a/web/packages/design/src/theme/themes/lightTheme.ts +++ b/web/packages/design/src/theme/themes/lightTheme.ts @@ -202,6 +202,7 @@ const colors: ThemeColors = { tooltip: { background: 'rgba(0, 0, 0, 0.80)', + inverseBackground: 'rgba(255, 255, 255, 0.5)', }, progressBarColor: '#007D6B', diff --git a/web/packages/design/src/theme/themes/types.ts b/web/packages/design/src/theme/themes/types.ts index 64a45651a7035..d0519eb98d3dc 100644 --- a/web/packages/design/src/theme/themes/types.ts +++ b/web/packages/design/src/theme/themes/types.ts @@ -141,6 +141,7 @@ export type ThemeColors = { tooltip: { background: string; + inverseBackground: string; }; progressBarColor: string; diff --git a/web/packages/shared/components/ButtonTextWithAddIcon/ButtonTextWithAddIcon.tsx b/web/packages/shared/components/ButtonTextWithAddIcon/ButtonTextWithAddIcon.tsx index 3d65b59944f06..e33a9849b37b4 100644 --- a/web/packages/shared/components/ButtonTextWithAddIcon/ButtonTextWithAddIcon.tsx +++ b/web/packages/shared/components/ButtonTextWithAddIcon/ButtonTextWithAddIcon.tsx @@ -33,7 +33,7 @@ export const ButtonTextWithAddIcon = ({ iconSize?: IconSize; }) => { return ( - + . + */ + +import { ResourceLabelTooltip } from './ResourceLabelTooltip'; + +export default { + title: 'Teleport/Discover/Shared/ResourceLabelTooltip', +}; + +export const RDS = () => ; +export const EKS = () => ; +export const Server = () => ; +export const Database = () => ; +export const Kube = () => ; diff --git a/web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/ResourceLabelTooltip.tsx b/web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/ResourceLabelTooltip.tsx new file mode 100644 index 0000000000000..2e7ad2d1980aa --- /dev/null +++ b/web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/ResourceLabelTooltip.tsx @@ -0,0 +1,147 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import Link from 'design/Link'; +import { MarkInverse } from 'design/Mark'; +import { Position } from 'design/Popover/Popover'; +import { IconTooltip } from 'design/Tooltip'; +import styled from 'styled-components'; + +/** + * Returns a IconTooltip component with its tip contents + * set to the requested resource kind. + * + * @param resourceKind - the tip contents differ slightly depending + * on the resource kind + * @param toolTipPosition (opt) - the position which the tooltip should + * render (see type Position) + * @returns JSX Element + */ +export function ResourceLabelTooltip({ + resourceKind, + toolTipPosition, +}: { + resourceKind: 'server' | 'eks' | 'rds' | 'kube' | 'db'; + toolTipPosition?: Position; +}) { + let tip; + + switch (resourceKind) { + case 'server': { + tip = ( + <> + Labels allow you to do the following: +
    +
  • + Filter servers by labels when using tsh, tctl, or the web UI. +
  • +
  • + Restrict access to this server with{' '} + + Teleport RBAC + + . Only roles with node_labels that + match these labels will be allowed to access this server. +
  • +
+ + ); + break; + } + case 'kube': + case 'eks': { + tip = ( + <> + Labels allow you to do the following: +
    +
  • + Filter Kubernetes clusters by labels when using tsh, tctl, or the + web UI. +
  • +
  • + Restrict access to this Kubernetes cluster with{' '} + + Teleport RBAC + + . Only roles with kubernetes_labels{' '} + that match these labels will be allowed to access this Kubernetes + cluster. +
  • + {resourceKind === 'eks' && ( +
  • + All the AWS tags from the selected EKS will be included upon + enrollment. +
  • + )} +
+ + ); + break; + } + case 'rds': + case 'db': { + tip = ( + <> + Labels allow you to do the following: +
    +
  • + Filter databases by labels when using tsh, tctl, or the web UI. +
  • +
  • + Restrict access to this database with{' '} + + Teleport RBAC + + . Only roles with db_labels that match + these labels will be allowed to access this database. +
  • + {resourceKind === 'rds' && ( +
  • + All the AWS tags from the selected RDS will be included upon + enrollment. +
  • + )} +
+ + ); + break; + } + default: + resourceKind satisfies never; + } + + return ( + + {tip} + + ); +} + +const Ul = styled.ul` + margin: 0; + padding-left: ${p => p.theme.space[4]}px; +`; diff --git a/web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/index.ts b/web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/index.ts new file mode 100644 index 0000000000000..fd093b71e1080 --- /dev/null +++ b/web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/index.ts @@ -0,0 +1,19 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export { ResourceLabelTooltip } from './ResourceLabelTooltip'; From ae33819110d47c3fe3f780eb6e559ad60ddfe28a Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Mon, 30 Dec 2024 12:39:50 -0700 Subject: [PATCH 20/30] Add a --labels flag to tctl tokens ls (#50624) Note: I removed the subtests from TestTokens since they were not independendent tests - the ls subtest could only succeed if the add subtest ran first. Closes #46388 --- tool/tctl/common/token_command.go | 18 +++++ tool/tctl/common/token_command_test.go | 94 +++++++++++++------------- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/tool/tctl/common/token_command.go b/tool/tctl/common/token_command.go index c11a64ce070cc..29f192512e09a 100644 --- a/tool/tctl/common/token_command.go +++ b/tool/tctl/common/token_command.go @@ -24,6 +24,7 @@ import ( "fmt" "io" "os" + "slices" "sort" "strings" "text/template" @@ -139,6 +140,7 @@ func (c *TokensCommand) Initialize(app *kingpin.Application, config *servicecfg. c.tokenList = tokens.Command("ls", "List node and user invitation tokens.") c.tokenList.Flag("format", "Output format, 'text', 'json' or 'yaml'").EnumVar(&c.format, formats...) c.tokenList.Flag("with-secrets", "Do not redact join tokens").BoolVar(&c.withSecrets) + c.tokenList.Flag("labels", labelHelp).StringVar(&c.labels) if c.stdout == nil { c.stdout = os.Stdout @@ -375,10 +377,26 @@ func (c *TokensCommand) Del(ctx context.Context, client *authclient.Client) erro // List is called to execute "tokens ls" command. func (c *TokensCommand) List(ctx context.Context, client *authclient.Client) error { + labels, err := libclient.ParseLabelSpec(c.labels) + if err != nil { + return trace.Wrap(err) + } + tokens, err := client.GetTokens(ctx) if err != nil { return trace.Wrap(err) } + + tokens = slices.DeleteFunc(tokens, func(token types.ProvisionToken) bool { + tokenLabels := token.GetMetadata().Labels + for k, v := range labels { + if tokenLabels[k] != v { + return true + } + } + return false + }) + if len(tokens) == 0 && c.format == teleport.Text { fmt.Fprintln(c.stdout, "No active tokens found.") return nil diff --git a/tool/tctl/common/token_command_test.go b/tool/tctl/common/token_command_test.go index aef8e3556b21e..afc84f963d4a4 100644 --- a/tool/tctl/common/token_command_test.go +++ b/tool/tctl/common/token_command_test.go @@ -82,58 +82,60 @@ func TestTokens(t *testing.T) { clt := testenv.MakeDefaultAuthClient(t, process) // Test all output formats of "tokens add". - t.Run("add", func(t *testing.T) { - buf, err := runTokensCommand(t, clt, []string{"add", "--type=node"}) - require.NoError(t, err) - require.True(t, strings.HasPrefix(buf.String(), "The invite token:")) + buf, err := runTokensCommand(t, clt, []string{"add", "--type=node"}) + require.NoError(t, err) + require.True(t, strings.HasPrefix(buf.String(), "The invite token:")) - buf, err = runTokensCommand(t, clt, []string{"add", "--type=node,app", "--format", teleport.Text}) - require.NoError(t, err) - require.Equal(t, 1, strings.Count(buf.String(), "\n")) + buf, err = runTokensCommand(t, clt, []string{"add", "--type=node,app", "--format", teleport.Text}) + require.NoError(t, err) + require.Equal(t, 1, strings.Count(buf.String(), "\n")) - buf, err = runTokensCommand(t, clt, []string{"add", "--type=node,app", "--format", teleport.JSON}) - require.NoError(t, err) - out := mustDecodeJSON[addedToken](t, buf) + buf, err = runTokensCommand(t, clt, []string{"add", "--type=node,app", "--format", teleport.JSON}) + require.NoError(t, err) + out := mustDecodeJSON[addedToken](t, buf) - require.Len(t, out.Roles, 2) - require.Equal(t, types.KindNode, strings.ToLower(out.Roles[0])) - require.Equal(t, types.KindApp, strings.ToLower(out.Roles[1])) + require.Len(t, out.Roles, 2) + require.Equal(t, types.KindNode, strings.ToLower(out.Roles[0])) + require.Equal(t, types.KindApp, strings.ToLower(out.Roles[1])) - buf, err = runTokensCommand(t, clt, []string{"add", "--type=node,app", "--format", teleport.YAML}) - require.NoError(t, err) - out = mustDecodeYAML[addedToken](t, buf) + buf, err = runTokensCommand(t, clt, []string{"add", "--type=node,app", "--format", teleport.YAML}) + require.NoError(t, err) + out = mustDecodeYAML[addedToken](t, buf) - require.Len(t, out.Roles, 2) - require.Equal(t, types.KindNode, strings.ToLower(out.Roles[0])) - require.Equal(t, types.KindApp, strings.ToLower(out.Roles[1])) + require.Len(t, out.Roles, 2) + require.Equal(t, types.KindNode, strings.ToLower(out.Roles[0])) + require.Equal(t, types.KindApp, strings.ToLower(out.Roles[1])) - buf, err = runTokensCommand(t, clt, []string{"add", "--type=kube"}) - require.NoError(t, err) - require.Contains(t, buf.String(), `--set roles="kube\,app\,discovery"`, - "Command print out should include setting kube, app and discovery roles for helm install.") - }) + buf, err = runTokensCommand(t, clt, []string{"add", "--type=kube", "--labels=foo=bar"}) + require.NoError(t, err) + require.Contains(t, buf.String(), `--set roles="kube\,app\,discovery"`, + "Command print out should include setting kube, app and discovery roles for helm install.") // Test all output formats of "tokens ls". - t.Run("ls", func(t *testing.T) { - buf, err := runTokensCommand(t, clt, []string{"ls"}) - require.NoError(t, err) - require.True(t, strings.HasPrefix(buf.String(), "Token ")) - require.Equal(t, 7, strings.Count(buf.String(), "\n")) // account for header lines - - buf, err = runTokensCommand(t, clt, []string{"ls", "--format", teleport.Text}) - require.NoError(t, err) - require.Equal(t, 5, strings.Count(buf.String(), "\n")) - - buf, err = runTokensCommand(t, clt, []string{"ls", "--format", teleport.JSON}) - require.NoError(t, err) - jsonOut := mustDecodeJSON[[]listedToken](t, buf) - require.Len(t, jsonOut, 5) - - buf, err = runTokensCommand(t, clt, []string{"ls", "--format", teleport.YAML}) - require.NoError(t, err) - yamlOut := []listedToken{} - mustDecodeYAMLDocuments(t, buf, &yamlOut) - require.Len(t, yamlOut, 5) - require.Equal(t, jsonOut, yamlOut) - }) + buf, err = runTokensCommand(t, clt, []string{"ls"}) + require.NoError(t, err) + require.True(t, strings.HasPrefix(buf.String(), "Token ")) + require.Equal(t, 7, strings.Count(buf.String(), "\n")) // account for header lines + + buf, err = runTokensCommand(t, clt, []string{"ls", "--format", teleport.Text}) + require.NoError(t, err) + require.Equal(t, 5, strings.Count(buf.String(), "\n")) + + buf, err = runTokensCommand(t, clt, []string{"ls", "--format", teleport.JSON}) + require.NoError(t, err) + jsonOut := mustDecodeJSON[[]listedToken](t, buf) + require.Len(t, jsonOut, 5) + + buf, err = runTokensCommand(t, clt, []string{"ls", "--format", teleport.YAML}) + require.NoError(t, err) + yamlOut := []listedToken{} + mustDecodeYAMLDocuments(t, buf, &yamlOut) + require.Len(t, yamlOut, 5) + require.Equal(t, jsonOut, yamlOut) + + // Test filtering by label + buf, err = runTokensCommand(t, clt, []string{"ls", "--format", teleport.JSON, "--labels=foo=bar"}) + require.NoError(t, err) + jsonOut = mustDecodeJSON[[]listedToken](t, buf) + require.Len(t, jsonOut, 1) } From 9d6e07f5b15b39a4ab5c7aebc8cd999846270e0e Mon Sep 17 00:00:00 2001 From: "teleport-post-release-automation[bot]" <128860004+teleport-post-release-automation[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:41:51 +0000 Subject: [PATCH 21/30] [auto] docs: Update version to v17.1.2 (#50632) Co-authored-by: GitHub --- docs/config.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/config.json b/docs/config.json index ebc0f3b033a10..83d00196b9230 100644 --- a/docs/config.json +++ b/docs/config.json @@ -185,18 +185,18 @@ "teleport": { "git": "api/14.0.0-gd1e081e", "major_version": "17", - "version": "17.1.1", + "version": "17.1.2", "url": "teleport.example.com", "golang": "1.23.4", "plugin": { - "version": "17.1.1" + "version": "17.1.2" }, "helm_repo_url": "https://charts.releases.teleport.dev", - "latest_oss_docker_image": "public.ecr.aws/gravitational/teleport-distroless:17.1.1", - "latest_oss_debug_docker_image": "public.ecr.aws/gravitational/teleport-distroless-debug:17.1.1", - "latest_ent_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless:17.1.1", - "latest_ent_debug_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless-debug:17.1.1", - "teleport_install_script_url": "https://cdn.teleport.dev/install-v17.1.1.sh" + "latest_oss_docker_image": "public.ecr.aws/gravitational/teleport-distroless:17.1.2", + "latest_oss_debug_docker_image": "public.ecr.aws/gravitational/teleport-distroless-debug:17.1.2", + "latest_ent_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless:17.1.2", + "latest_ent_debug_docker_image": "public.ecr.aws/gravitational/teleport-ent-distroless-debug:17.1.2", + "teleport_install_script_url": "https://cdn.teleport.dev/install-v17.1.2.sh" }, "terraform": { "version": "1.0.0" From 6dd131d58a7a01880f2635f2e1efa04a00726e4b Mon Sep 17 00:00:00 2001 From: Steven Martin Date: Mon, 30 Dec 2024 16:59:35 -0500 Subject: [PATCH 22/30] [web] update edit token advisory on YAML (#50274) --- web/packages/teleport/src/JoinTokens/JoinTokens.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/teleport/src/JoinTokens/JoinTokens.tsx b/web/packages/teleport/src/JoinTokens/JoinTokens.tsx index 862085cfd0157..1dbcc8b725ad2 100644 --- a/web/packages/teleport/src/JoinTokens/JoinTokens.tsx +++ b/web/packages/teleport/src/JoinTokens/JoinTokens.tsx @@ -446,7 +446,7 @@ const ActionCell = ({ function Directions() { return ( <> - WARNING Roles are defined using{' '} + WARNING Tokens are defined using{' '} Date: Tue, 31 Dec 2024 10:30:57 +0000 Subject: [PATCH 23/30] [v17] gha(amplify-preview): use custom action to generate Amplify previews for Teleport Docs (#50547) * Prepare docs preview URLs using custom `amplify-preview` GHA * Add failure message * Update .github/workflows/docs-amplify.yaml Co-authored-by: Zac Bergquist --------- Co-authored-by: Zac Bergquist --- .github/workflows/docs-amplify.yaml | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/docs-amplify.yaml diff --git a/.github/workflows/docs-amplify.yaml b/.github/workflows/docs-amplify.yaml new file mode 100644 index 0000000000000..3ca12933b50c1 --- /dev/null +++ b/.github/workflows/docs-amplify.yaml @@ -0,0 +1,42 @@ +name: Docs Preview +on: + pull_request: + paths: + - 'docs/**' + - .github/workflows/docs-amplify.yaml + workflow_dispatch: + +permissions: + pull-requests: write + id-token: write + +jobs: + amplify-preview: + name: Prepare Amplify preview URL + runs-on: ubuntu-22.04-2core-arm64 + environment: docs-amplify + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4 + with: + aws-region: us-west-2 + role-to-assume: ${{ vars.IAM_ROLE }} + + - name: Create Amplify preview environment + uses: gravitational/shared-workflows/tools/amplify-preview@tools/amplify-preview/v0.0.1 + continue-on-error: true + with: + app_ids: ${{ vars.AMPLIFY_APP_IDS }} + create_branches: "true" + github_token: ${{ secrets.GITHUB_TOKEN }} + wait: "true" + + - name: Print failure message + if: failure() + env: + ERR_TITLE: Teleport Docs preview build failed + ERR_MESSAGE: >- + Please refer to the following documentation for help: https://www.notion.so/goteleport/How-to-Amplify-deployments-162fdd3830be8096ba72efa1a49ee7bc?pvs=4 + run: | + echo ::error title=$ERR_TITLE::$ERR_MESSAGE + exit 1 From 7912a9718cfe7931d60d641bcee14e901bbb1416 Mon Sep 17 00:00:00 2001 From: matheus Date: Tue, 31 Dec 2024 09:35:16 -0300 Subject: [PATCH 24/30] Show cluster management page on dashboards (#50626) --- web/packages/teleport/src/features.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/teleport/src/features.tsx b/web/packages/teleport/src/features.tsx index 7c26132fcf600..5f8f4d41eadef 100644 --- a/web/packages/teleport/src/features.tsx +++ b/web/packages/teleport/src/features.tsx @@ -569,7 +569,7 @@ export class FeatureClusters implements TeleportFeature { }; hasAccess(flags: FeatureFlags) { - return flags.trustedClusters; + return cfg.isDashboard || flags.trustedClusters; } navigationItem = { From bc29a6c1b2f65b5c755a9e73a4eff7c407d16db0 Mon Sep 17 00:00:00 2001 From: Leszek Charkiewicz Date: Tue, 31 Dec 2024 18:02:57 +0100 Subject: [PATCH 25/30] Prevent panicking when SQS consumer is disabled (#50648) --- lib/events/athena/athena.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/events/athena/athena.go b/lib/events/athena/athena.go index 0fa5416a5e1cb..12eb268708a25 100644 --- a/lib/events/athena/athena.go +++ b/lib/events/athena/athena.go @@ -541,7 +541,11 @@ func (l *Log) SearchSessionEvents(ctx context.Context, req events.SearchSessionE } func (l *Log) Close() error { - return trace.Wrap(l.consumerCloser.Close()) + // consumerCloser is nil when consumer is disabled. + if l.consumerCloser != nil { + return trace.Wrap(l.consumerCloser.Close()) + } + return nil } func (l *Log) IsConsumerDisabled() bool { From 6d6316237086b5691780ec9edafc3104e45b774a Mon Sep 17 00:00:00 2001 From: Logan Davis <38335829+logand22@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:56:52 -0800 Subject: [PATCH 26/30] [16.4.11] bump cloud docs (#50638) --- docs/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.json b/docs/config.json index 83d00196b9230..8dc4c113415a8 100644 --- a/docs/config.json +++ b/docs/config.json @@ -135,7 +135,7 @@ "aws_secret_access_key": "zyxw9876-this-is-an-example" }, "cloud": { - "version": "16.4.9", + "version": "16.4.11", "major_version": "16", "sla": { "monthly_percentage": "99.9%", From ad9a7d17cb05c89482208173d3cc6c635e081762 Mon Sep 17 00:00:00 2001 From: matheus Date: Tue, 31 Dec 2024 16:36:46 -0300 Subject: [PATCH 27/30] update e ref (#50653) --- e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e b/e index fc7a7214b1fdc..0bfbd98dde0d6 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit fc7a7214b1fdcf7e6dd6b48a759247ac6355b417 +Subproject commit 0bfbd98dde0d6d20e7139234889d5df574da531c From 5b64a9c5655736a9ef66841346a21b392a0767aa Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:47:13 -0500 Subject: [PATCH 28/30] update auth connectors page (#50628) --- web/packages/design/src/Icon/Icons.story.tsx | 2 + .../design/src/Icon/Icons/MinusCircle.tsx | 67 ++++++ .../design/src/Icon/Icons/RocketLaunch.tsx | 67 ++++++ .../design/src/Icon/assets/MinusCircle.svg | 4 + .../design/src/Icon/assets/RocketLaunch.svg | 4 + web/packages/design/src/Icon/index.ts | 2 + .../design/src/Icon/script/IconTemplate.txt | 2 - .../src/ResourceIcon/assets/okta-alt.svg | 9 + web/packages/design/src/ResourceIcon/icons.ts | 2 + .../src/ResourceIcon/resourceIconSpecs.ts | 1 + .../src/AuthConnectors/AuthConnectorTile.tsx | 210 ++++++++++++++++++ .../src/AuthConnectors/AuthConnectors.tsx | 62 +++--- .../ConnectorList/CTAConnectors.tsx | 120 ++++++++++ .../ConnectorList/ConnectorList.tsx | 73 ++---- .../AuthConnectors/EmptyList/EmptyList.tsx | 83 ++----- .../AuthConnectors/ssoIcons/getSsoIcon.tsx | 169 ++++++++------ .../styles/ConnectorBox.styles.ts | 72 ------ 17 files changed, 665 insertions(+), 284 deletions(-) create mode 100644 web/packages/design/src/Icon/Icons/MinusCircle.tsx create mode 100644 web/packages/design/src/Icon/Icons/RocketLaunch.tsx create mode 100644 web/packages/design/src/Icon/assets/MinusCircle.svg create mode 100644 web/packages/design/src/Icon/assets/RocketLaunch.svg create mode 100644 web/packages/design/src/ResourceIcon/assets/okta-alt.svg create mode 100644 web/packages/teleport/src/AuthConnectors/AuthConnectorTile.tsx create mode 100644 web/packages/teleport/src/AuthConnectors/ConnectorList/CTAConnectors.tsx delete mode 100644 web/packages/teleport/src/AuthConnectors/styles/ConnectorBox.styles.ts diff --git a/web/packages/design/src/Icon/Icons.story.tsx b/web/packages/design/src/Icon/Icons.story.tsx index 47807a6715ae9..b405bec830973 100644 --- a/web/packages/design/src/Icon/Icons.story.tsx +++ b/web/packages/design/src/Icon/Icons.story.tsx @@ -163,6 +163,7 @@ export const Icons = () => ( + @@ -186,6 +187,7 @@ export const Icons = () => ( + diff --git a/web/packages/design/src/Icon/Icons/MinusCircle.tsx b/web/packages/design/src/Icon/Icons/MinusCircle.tsx new file mode 100644 index 0000000000000..fe3a181ffd912 --- /dev/null +++ b/web/packages/design/src/Icon/Icons/MinusCircle.tsx @@ -0,0 +1,67 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* MIT License + +Copyright (c) 2020 Phosphor Icons + +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. + +*/ + +import { Icon, IconProps } from '../Icon'; + +/* + +THIS FILE IS GENERATED. DO NOT EDIT. + +*/ + +export function MinusCircle({ size = 24, color, ...otherProps }: IconProps) { + return ( + + + + + ); +} diff --git a/web/packages/design/src/Icon/Icons/RocketLaunch.tsx b/web/packages/design/src/Icon/Icons/RocketLaunch.tsx new file mode 100644 index 0000000000000..500dc5d066453 --- /dev/null +++ b/web/packages/design/src/Icon/Icons/RocketLaunch.tsx @@ -0,0 +1,67 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* MIT License + +Copyright (c) 2020 Phosphor Icons + +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. + +*/ + +import { Icon, IconProps } from '../Icon'; + +/* + +THIS FILE IS GENERATED. DO NOT EDIT. + +*/ + +export function RocketLaunch({ size = 24, color, ...otherProps }: IconProps) { + return ( + + + + + ); +} diff --git a/web/packages/design/src/Icon/assets/MinusCircle.svg b/web/packages/design/src/Icon/assets/MinusCircle.svg new file mode 100644 index 0000000000000..1058d09b4e10d --- /dev/null +++ b/web/packages/design/src/Icon/assets/MinusCircle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/packages/design/src/Icon/assets/RocketLaunch.svg b/web/packages/design/src/Icon/assets/RocketLaunch.svg new file mode 100644 index 0000000000000..e25ea62128692 --- /dev/null +++ b/web/packages/design/src/Icon/assets/RocketLaunch.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/packages/design/src/Icon/index.ts b/web/packages/design/src/Icon/index.ts index be8d7320be71b..79a733b145bb2 100644 --- a/web/packages/design/src/Icon/index.ts +++ b/web/packages/design/src/Icon/index.ts @@ -149,6 +149,7 @@ export { MagnifyingMinus } from './Icons/MagnifyingMinus'; export { MagnifyingPlus } from './Icons/MagnifyingPlus'; export { Memory } from './Icons/Memory'; export { Minus } from './Icons/Minus'; +export { MinusCircle } from './Icons/MinusCircle'; export { Moon } from './Icons/Moon'; export { MoreHoriz } from './Icons/MoreHoriz'; export { MoreVert } from './Icons/MoreVert'; @@ -172,6 +173,7 @@ export { PushPinFilled } from './Icons/PushPinFilled'; export { Question } from './Icons/Question'; export { Refresh } from './Icons/Refresh'; export { Restore } from './Icons/Restore'; +export { RocketLaunch } from './Icons/RocketLaunch'; export { Rows } from './Icons/Rows'; export { Ruler } from './Icons/Ruler'; export { Run } from './Icons/Run'; diff --git a/web/packages/design/src/Icon/script/IconTemplate.txt b/web/packages/design/src/Icon/script/IconTemplate.txt index 7ba34ed3fa097..e204eb99dfa0e 100644 --- a/web/packages/design/src/Icon/script/IconTemplate.txt +++ b/web/packages/design/src/Icon/script/IconTemplate.txt @@ -40,8 +40,6 @@ SOFTWARE. */ -import React from 'react'; - import { Icon, IconProps } from '../Icon'; /* diff --git a/web/packages/design/src/ResourceIcon/assets/okta-alt.svg b/web/packages/design/src/ResourceIcon/assets/okta-alt.svg new file mode 100644 index 0000000000000..c3c6e69a08c80 --- /dev/null +++ b/web/packages/design/src/ResourceIcon/assets/okta-alt.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/packages/design/src/ResourceIcon/icons.ts b/web/packages/design/src/ResourceIcon/icons.ts index fccf86a27aeff..88c24d2a33ecb 100644 --- a/web/packages/design/src/ResourceIcon/icons.ts +++ b/web/packages/design/src/ResourceIcon/icons.ts @@ -211,6 +211,7 @@ import notion from './assets/notion.svg'; import oasisopen from './assets/oasisopen.svg'; import oktaDark from './assets/okta-dark.svg'; import oktaLight from './assets/okta-light.svg'; +import oktaAlt from './assets/okta-alt.svg'; import onehundredonedomain from './assets/onehundredonedomain.svg'; import oneloginDark from './assets/onelogin-dark.svg'; import oneloginLight from './assets/onelogin-light.svg'; @@ -499,6 +500,7 @@ export { oasisopen, oktaDark, oktaLight, + oktaAlt, onehundredonedomain, oneloginDark, oneloginLight, diff --git a/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts b/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts index 67021894b7ad9..ab45fd7d8d4b9 100644 --- a/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts +++ b/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts @@ -196,6 +196,7 @@ export const resourceIconSpecs = { oasisopen: forAllThemes(i.oasisopen), okta: { dark: i.oktaDark, light: i.oktaLight }, + oktaAlt: forAllThemes(i.oktaAlt), '101domain': forAllThemes(i.onehundredonedomain), onelogin: { dark: i.oneloginDark, light: i.oneloginLight }, '1password': { dark: i.onepasswordDark, light: i.onepasswordLight }, diff --git a/web/packages/teleport/src/AuthConnectors/AuthConnectorTile.tsx b/web/packages/teleport/src/AuthConnectors/AuthConnectorTile.tsx new file mode 100644 index 0000000000000..e69ab077c8219 --- /dev/null +++ b/web/packages/teleport/src/AuthConnectors/AuthConnectorTile.tsx @@ -0,0 +1,210 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import styled, { useTheme } from 'styled-components'; + +import { Flex, H2, Subtitle2, ButtonSecondary, P3, Box } from 'design'; +import { ArrowRight, CircleCheck, Password } from 'design/Icon'; + +import { MenuIcon, MenuItem } from 'shared/components/MenuAction'; +import { AuthType } from 'shared/services'; + +import { State as ResourceState } from 'teleport/components/useResources'; + +export function AuthConnectorTile({ + id, + name, + kind, + Icon, + isDefault, + isPlaceholder, + onSetup, + customDesc, + onEdit, + onDelete, +}: { + name: string; + id: string; + kind: AuthType; + Icon: () => JSX.Element; + isDefault: boolean; + /** + * isPlaceholder is whether this isn't a real existing connector, but a placeholder as a shortcut to set one up. + */ + isPlaceholder: boolean; + onSetup?: () => void; + customDesc?: string; + onEdit?: ResourceState['edit']; + onDelete?: ResourceState['remove']; +}) { + const theme = useTheme(); + const onClickEdit = () => onEdit(id); + const onClickDelete = () => onDelete(id); + + let desc: string; + switch (kind) { + case 'github': + desc = 'GitHub Connector'; + break; + case 'oidc': + desc = 'OIDC Connector'; + break; + case 'saml': + desc = 'SAML Connector'; + break; + default: + kind satisfies never | 'local'; + } + + return ( + + + + + +

{name}

+ {isDefault && } +
+ + {customDesc || desc} + +
+
+ + {isPlaceholder && !!onSetup && ( + + Set Up + + )} + {!isPlaceholder && !!onEdit && !!onDelete && ( + + Edit + Delete + + )} + +
+ ); +} + +/** + * LocalConnectorTile is a hardcoded "auth connector" which represents local auth. + */ +export function LocalConnectorTile() { + return ( + ( + + props.theme.colors.interactive.tonal.neutral[0]}; + height: 61px; + width: 61px; + `} + lineHeight={0} + p={2} + borderRadius={3} + > + + + )} + isDefault={true} + isPlaceholder={false} + name="Local Connector" + customDesc="Manual auth w/ users local to Teleport" + /> + ); +} + +export const ConnectorBox = styled(Box)<{ disabled?: boolean }>` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + font-family: ${props => props.theme.font}; + padding: ${p => p.theme.space[3]}px; + transition: all 0.3s; + border-radius: ${props => props.theme.radii[2]}px; + border: ${props => props.theme.borders[2]} + ${props => props.theme.colors.interactive.tonal.neutral[0]}; + + &:hover { + background: ${props => props.theme.colors.levels.surface}; + border: ${props => props.theme.borders[2]} transparent; + } + + &:focus-visible { + outline: none; + border: ${props => props.theme.borders[2]} + ${props => props.theme.colors.text.muted}; + } + + &:active { + outline: none; + background: ${props => props.theme.colors.levels.surface}; + border: ${props => props.theme.borders[2]} + ${props => props.theme.colors.interactive.tonal.neutral[1]}; + } +`; + +function DefaultIndicator() { + return ( + + props.theme.colors.interactive.tonal.success[1]}; + border-radius: 62px; + `} + > + + + Default + + + ); +} diff --git a/web/packages/teleport/src/AuthConnectors/AuthConnectors.tsx b/web/packages/teleport/src/AuthConnectors/AuthConnectors.tsx index fc16cfe7d20a3..6f0b297c4bdf5 100644 --- a/web/packages/teleport/src/AuthConnectors/AuthConnectors.tsx +++ b/web/packages/teleport/src/AuthConnectors/AuthConnectors.tsx @@ -18,7 +18,7 @@ import { Alert, Box, Flex, H3, Indicator, Link } from 'design'; -import { P } from 'design/Text/Text'; +import { H2, P } from 'design/Text/Text'; import { FeatureBox, FeatureHeaderTitle } from 'teleport/components/Layout'; import ResourceEditor from 'teleport/components/ResourceEditor'; @@ -36,6 +36,7 @@ import ConnectorList from './ConnectorList'; import DeleteConnectorDialog from './DeleteConnectorDialog'; import useAuthConnectors, { State } from './useAuthConnectors'; import templates from './templates'; +import CTAConnectors from './ConnectorList/CTAConnectors'; export function AuthConnectorsContainer() { const state = useAuthConnectors(); @@ -80,34 +81,37 @@ export function AuthConnectors(props: State) { )} {attempt.status === 'success' && ( - {isEmpty && ( - - resources.create('github')} /> - - )} - <> - - -

Auth Connectors

-

{description}

-

- Please{' '} - - view our documentation - {' '} - on how to configure a GitHub connector. -

-
- + + +

Your Connectors

+ {isEmpty ? ( + resources.create('github')} /> + ) : ( + + )} +
+ +
+ +

Auth Connectors

+

{description}

+

+ Please{' '} + + view our documentation + {' '} + on how to configure a GitHub connector. +

+
)} {(resources.status === 'creating' || resources.status === 'editing') && ( diff --git a/web/packages/teleport/src/AuthConnectors/ConnectorList/CTAConnectors.tsx b/web/packages/teleport/src/AuthConnectors/ConnectorList/CTAConnectors.tsx new file mode 100644 index 0000000000000..117b02e4236d6 --- /dev/null +++ b/web/packages/teleport/src/AuthConnectors/ConnectorList/CTAConnectors.tsx @@ -0,0 +1,120 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import styled from 'styled-components'; + +import Box from 'design/Box'; +import { ResourceIcon } from 'design/ResourceIcon'; +import { H2, Subtitle2 } from 'design/Text'; +import Flex from 'design/Flex'; +import { RocketLaunch } from 'design/Icon'; + +import { ButtonLockedFeature } from 'teleport/components/ButtonLockedFeature'; +import { CtaEvent } from 'teleport/services/userEvent'; + +export default function CTAConnectors() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + +

Unlock OIDC & SAML Single Sign-On with Teleport Enterprise

+ + Connect your identity provider to streamline employee onboarding, + simplify compliance, and monitor access patterns against a single + source of truth. + + + + Upgrade to Enterprise + +
+
+ ); +} + +const AuthConnectorsCTABox = styled(Box)` + width: 100%; + padding: ${p => p.theme.space[5]}px; + + border: ${props => props.theme.borders[1]}; + border-color: ${props => props.theme.colors.interactive.tonal.neutral[1]}; + border-radius: ${props => props.theme.radii[2]}px; + background: transparent; + + display: flex; + flex-direction: column; + align-items: flex-start; + gap: ${p => p.theme.space[3]}px; + align-self: stretch; +`; + +const CTALogosContainer = styled(Box)` + display: flex; + flex-direction: row; + width: 100%; + // We have to manually set a height here since the CTALogo's are 'position: absolute' and won't automatically size the container. + //padding (top and bottom) + height of the icon + height: ${p => p.theme.space[4] * 2 + 36}px; + + position: relative; +`; + +const CTALogo = styled(Box)` + background-color: ${props => props.theme.colors.levels.sunken}; + padding: ${p => p.theme.space[4]}px; + + display: flex; + align-items: center; + justify-content: center; + + border: ${props => props.theme.borders[1]}; + border-color: ${props => props.theme.colors.interactive.tonal.neutral[1]}; + border-radius: 50%; + + position: absolute; +`; diff --git a/web/packages/teleport/src/AuthConnectors/ConnectorList/ConnectorList.tsx b/web/packages/teleport/src/AuthConnectors/ConnectorList/ConnectorList.tsx index 39544ef9bda3b..8f3605b4044a1 100644 --- a/web/packages/teleport/src/AuthConnectors/ConnectorList/ConnectorList.tsx +++ b/web/packages/teleport/src/AuthConnectors/ConnectorList/ConnectorList.tsx @@ -16,23 +16,31 @@ * along with this program. If not, see . */ -import { Box, ButtonPrimary, Flex, ResourceIcon, Text } from 'design'; -import { MenuIcon, MenuItem } from 'shared/components/MenuAction'; +import styled from 'styled-components'; -import { State as ResourceState } from 'teleport/components/useResources'; +import { Box } from 'design'; -import { ResponsiveConnector } from 'teleport/AuthConnectors/styles/ConnectorBox.styles'; +import { State as ResourceState } from 'teleport/components/useResources'; import { State as AuthConnectorState } from '../useAuthConnectors'; +import { AuthConnectorTile, LocalConnectorTile } from '../AuthConnectorTile'; +import getSsoIcon from '../ssoIcons/getSsoIcon'; export default function ConnectorList({ items, onEdit, onDelete }: Props) { items = items || []; const $items = items.map(item => { - const { id, name } = item; + const { id, name, kind } = item; + + const Icon = getSsoIcon(kind, name); + return ( - + + {$items} - - ); -} - -function ConnectorListItem({ name, id, onEdit, onDelete }) { - const onClickEdit = () => onEdit(id); - const onClickDelete = () => onDelete(id); - - return ( - - - - Delete... - - - - - - - - {name} - - - - Edit Connector - - + ); } -const menuActionProps = { - style: { - right: '10px', - position: 'absolute', - top: '10px', - }, -}; - type Props = { items: AuthConnectorState['items']; onEdit: ResourceState['edit']; onDelete: ResourceState['remove']; }; + +export const AuthConnectorsGrid = styled(Box)` + width: 100%; + display: grid; + gap: ${p => p.theme.space[3]}px; + grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); +`; diff --git a/web/packages/teleport/src/AuthConnectors/EmptyList/EmptyList.tsx b/web/packages/teleport/src/AuthConnectors/EmptyList/EmptyList.tsx index b3dd5ea561647..0751eb31347f8 100644 --- a/web/packages/teleport/src/AuthConnectors/EmptyList/EmptyList.tsx +++ b/web/packages/teleport/src/AuthConnectors/EmptyList/EmptyList.tsx @@ -16,78 +16,27 @@ * along with this program. If not, see . */ -import { Card, Flex, H1, ResourceIcon, Text } from 'design'; -import { AuthProviderType } from 'shared/services'; - -import { H2 } from 'design'; - -import { ConnectorBox } from 'teleport/AuthConnectors/styles/ConnectorBox.styles'; - -import { - LockedFeatureButton, - LockedFeatureContainer, -} from 'teleport/AuthConnectors/styles/LockedFeatureContainer.styles'; - import getSsoIcon from 'teleport/AuthConnectors/ssoIcons/getSsoIcon'; import { State as ResourceState } from 'teleport/components/useResources'; -import { CtaEvent } from 'teleport/services/userEvent'; - -export default function EmptyList({ onCreate }: Props) { - return ( - -

Select a service provider below

- - {renderGithubConnector(onCreate)} - - {renderLockedItem('oidc')} - {renderLockedItem('saml')} - - Unlock OIDC & SAML with Teleport Enterprise - - - -
- ); -} -function renderGithubConnector(onCreate) { - return ( - - - - - - +import { AuthConnectorTile, LocalConnectorTile } from '../AuthConnectorTile'; +import { AuthConnectorsGrid } from '../ConnectorList/ConnectorList'; -

GitHub

- { - - Sign in using your GitHub account - - } -
- ); -} - -function renderLockedItem(kind: AuthProviderType) { - const { desc, SsoIcon, info } = getSsoIcon(kind); +export default function EmptyList({ onCreate }: Props) { return ( - - - - -

{desc}

- {info && ( - - {info} - - )} -
+ + + onCreate('github')} + /> + ); } diff --git a/web/packages/teleport/src/AuthConnectors/ssoIcons/getSsoIcon.tsx b/web/packages/teleport/src/AuthConnectors/ssoIcons/getSsoIcon.tsx index 27fae0e56fd9a..fbb116b59f3f2 100644 --- a/web/packages/teleport/src/AuthConnectors/ssoIcons/getSsoIcon.tsx +++ b/web/packages/teleport/src/AuthConnectors/ssoIcons/getSsoIcon.tsx @@ -20,93 +20,132 @@ import styled from 'styled-components'; import { AuthProviderType } from 'shared/services'; import { Box, Flex, ResourceIcon } from 'design'; -export default function getSsoIcon(kind: AuthProviderType) { - const desc = formatConnectorTypeDesc(kind); +export default function getSsoIcon( + kind: AuthProviderType, + name?: string +): () => JSX.Element { + const guessedIcon = guessIconFromName(name || ''); + if (guessedIcon) { + return guessedIcon; + } switch (kind) { case 'github': - return { - SsoIcon: () => ( - - - - ), - desc, - info: 'Sign in using your GitHub account', - }; + return () => ( + + + + ); case 'saml': - return { - SsoIcon: () => ( - - - - - - - - - - - - - - - ), - desc, - info: 'Okta, OneLogin, Microsoft Entra ID, etc.', - }; + return () => ( + + + + + + + + + + + + + + + ); case 'oidc': default: - return { - SsoIcon: () => ( - - - - - - - - - - - - - - - ), - desc, - info: 'Google, GitLab, Amazon and more', - }; + return () => ( + + + + + + + + + + + + + + + ); } } -function formatConnectorTypeDesc(kind) { - kind = kind || ''; - if (kind == 'github') { - return `GitHub`; +function guessIconFromName(connectorName: string) { + const name = connectorName.toLocaleLowerCase(); + + if (name.includes('okta')) { + return () => ( + + + + ); + } + if ( + name.includes('entra') || + name.includes('active directory') || + name.includes('microsoft') || + name.includes('azure') + ) { + return () => ( + + + + ); + } + if (name.includes('google')) { + return () => ( + + + + ); + } + if (name.includes('gitlab')) { + return () => ( + + + + ); + } + if (name.includes('onelogin')) { + return () => ( + + + + ); + } + if (name.includes('auth0') || name.includes('authzero')) { + return () => ( + + + + ); } - return kind.toUpperCase(); } const MultiIconContainer = styled(Flex)` - width: 83px; + width: 61px; + height: 61px; flex-wrap: wrap; gap: 3px; - padding: 7px; - border: 1px solid rgba(255, 255, 255, 0.07); + padding: ${p => p.theme.space[2]}px; + border: 1px solid ${p => p.theme.colors.interactive.tonal.neutral[2]}; border-radius: 8px; `; const SmIcon = styled(Box)` - width: ${p => p.theme.space[5]}px; - height: ${p => p.theme.space[5]}px; - line-height: ${p => p.theme.space[5]}px; - background: ${p => p.theme.colors.levels.popout}; - border-radius: 50%; + width: 20px; + height: 20px; display: flex; justify-content: center; + align-items: center; `; const StyledResourceIcon = styled(ResourceIcon).attrs({ width: '20px', -})``; +})` + line-height: 0px !important; +`; diff --git a/web/packages/teleport/src/AuthConnectors/styles/ConnectorBox.styles.ts b/web/packages/teleport/src/AuthConnectors/styles/ConnectorBox.styles.ts deleted file mode 100644 index f88e5e7ad5646..0000000000000 --- a/web/packages/teleport/src/AuthConnectors/styles/ConnectorBox.styles.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { Box, Flex } from 'design'; -import styled from 'styled-components'; - -export const ConnectorBox = styled(Box)` - display: flex; - flex-direction: column; - font-family: ${props => props.theme.font}; - width: 320px; - padding: ${p => p.theme.space[4]}px; - margin: ${p => p.theme.space[3]}px ${p => p.theme.space[2]}px; - background: transparent; - transition: all 0.3s; - border-radius: ${props => props.theme.radii[2]}px; - min-height: 190px; - border: ${props => props.theme.borders[2]} - ${p => p.theme.colors.spotBackground[0]}; - - &:hover, - &:focus { - border: ${props => props.theme.borders[2]} - ${p => p.theme.colors.spotBackground[2]}; - background: ${p => p.theme.colors.spotBackground[0]}; - box-shadow: ${p => p.theme.boxShadow[3]}; - cursor: pointer; - } - - &:disabled { - cursor: not-allowed; - color: inherit; - font-family: inherit; - outline: none; - position: relative; - text-align: center; - text-decoration: none; - opacity: 0.24; - box-shadow: none; - } -`; - -export const ResponsiveConnector = styled(Flex)` - position: relative; - box-shadow: ${p => p.theme.boxShadow[5]}; - width: 240px; - height: 240px; - border-radius: ${props => props.theme.radii[2]}px; - flex-direction: column; - align-items: center; - justify-content: center; - background-color: ${props => props.theme.colors.levels.surface}; - padding: ${props => props.theme.space[5]}px; - @media screen and (max-width: ${props => props.theme.breakpoints.tablet}px) { - width: 100%; - } -`; From b80d41797bfd6adadef54315c8aa5ae0d5956ace Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Wed, 1 Jan 2025 15:26:32 +0000 Subject: [PATCH 29/30] [v17] Add `IssueWorkloadIdentities` RPC (#50639) * Start hacking on IssueWorkloadIdentities * Keep hacking on IssueWorkloadIdentities * Support some non-string attributes for rules and templatin * Start to extract decision functionality * Rearrange decision code into own file * Switch existing RPC to use same decision logic * Factor out SVID issuance code to be more reusable * Wire up actual issuance * Cache CA/issuing kjey * Some more tidying of code/rearranging * More code tidying * Add TestIssueWorkloadIdentities * Add additional rbac check * Fix rbac in tests * Fix spelling of labelled --- .../machineid/workloadidentityv1/decision.go | 169 +++++ ...ssuer_service_test.go => decision_test.go} | 0 .../workloadidentityv1/issuer_service.go | 611 +++++++++++------- .../workloadidentityv1_test.go | 398 +++++++++++- 4 files changed, 904 insertions(+), 274 deletions(-) create mode 100644 lib/auth/machineid/workloadidentityv1/decision.go rename lib/auth/machineid/workloadidentityv1/{issuer_service_test.go => decision_test.go} (100%) diff --git a/lib/auth/machineid/workloadidentityv1/decision.go b/lib/auth/machineid/workloadidentityv1/decision.go new file mode 100644 index 0000000000000..53a1a9c1bc5f6 --- /dev/null +++ b/lib/auth/machineid/workloadidentityv1/decision.go @@ -0,0 +1,169 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package workloadidentityv1 + +import ( + "context" + "regexp" + "slices" + "strings" + + "github.com/gravitational/trace" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" +) + +type decision struct { + templatedWorkloadIdentity *workloadidentityv1pb.WorkloadIdentity + shouldIssue bool + reason error +} + +func decide( + ctx context.Context, + wi *workloadidentityv1pb.WorkloadIdentity, + attrs *workloadidentityv1pb.Attrs, +) *decision { + d := &decision{ + templatedWorkloadIdentity: proto.Clone(wi).(*workloadidentityv1pb.WorkloadIdentity), + } + + // First, evaluate the rules. + if err := evaluateRules(wi, attrs); err != nil { + d.reason = trace.Wrap(err, "attributes did not pass rule evaluation") + return d + } + + // Now we can cook up some templating... + templated, err := templateString(wi.GetSpec().GetSpiffe().GetId(), attrs) + if err != nil { + d.reason = trace.Wrap(err, "templating spec.spiffe.id") + return d + } + d.templatedWorkloadIdentity.Spec.Spiffe.Id = templated + + templated, err = templateString(wi.GetSpec().GetSpiffe().GetHint(), attrs) + if err != nil { + d.reason = trace.Wrap(err, "templating spec.spiffe.hint") + return d + } + d.templatedWorkloadIdentity.Spec.Spiffe.Hint = templated + + // Yay - made it to the end! + d.shouldIssue = true + return d +} + +// getFieldStringValue returns a string value from the given attribute set. +// The attribute is specified as a dot-separated path to the field in the +// attribute set. +// +// The specified attribute must be a string field. If the attribute is not +// found, an error is returned. +// +// TODO(noah): This function will be replaced by the Teleport predicate language +// in a coming PR. +func getFieldStringValue(attrs *workloadidentityv1pb.Attrs, attr string) (string, error) { + attrParts := strings.Split(attr, ".") + message := attrs.ProtoReflect() + // TODO(noah): Improve errors by including the fully qualified attribute + // (e.g add up the parts of the attribute path processed thus far) + for i, part := range attrParts { + fieldDesc := message.Descriptor().Fields().ByTextName(part) + if fieldDesc == nil { + return "", trace.NotFound("attribute %q not found", part) + } + // We expect the final key to point to a string field - otherwise - we + // return an error. + if i == len(attrParts)-1 { + if !slices.Contains([]protoreflect.Kind{ + protoreflect.StringKind, + protoreflect.BoolKind, + protoreflect.Int32Kind, + protoreflect.Int64Kind, + protoreflect.Uint64Kind, + protoreflect.Uint32Kind, + }, fieldDesc.Kind()) { + return "", trace.BadParameter("attribute %q of type %q cannot be converted to string", part, fieldDesc.Kind()) + } + return message.Get(fieldDesc).String(), nil + } + // If we're not processing the final key part, we expect this to point + // to a message that we can further explore. + if fieldDesc.Kind() != protoreflect.MessageKind { + return "", trace.BadParameter("attribute %q is not a message", part) + } + message = message.Get(fieldDesc).Message() + } + return "", nil +} + +// templateString takes a given input string and replaces any values within +// {{ }} with values from the attribute set. +// +// If the specified value is not found in the attribute set, an error is +// returned. +// +// TODO(noah): In a coming PR, this will be replaced by evaluating the values +// within the handlebars as expressions. +func templateString(in string, attrs *workloadidentityv1pb.Attrs) (string, error) { + re := regexp.MustCompile(`\{\{([^{}]+?)\}\}`) + matches := re.FindAllStringSubmatch(in, -1) + + for _, match := range matches { + attrKey := strings.TrimSpace(match[1]) + value, err := getFieldStringValue(attrs, attrKey) + if err != nil { + return "", trace.Wrap(err, "fetching attribute value for %q", attrKey) + } + // We want to have an implicit rule here that if an attribute is + // included in the template, but is not set, we should refuse to issue + // the credential. + if value == "" { + return "", trace.NotFound("attribute %q unset", attrKey) + } + in = strings.Replace(in, match[0], value, 1) + } + + return in, nil +} + +func evaluateRules( + wi *workloadidentityv1pb.WorkloadIdentity, + attrs *workloadidentityv1pb.Attrs, +) error { + if len(wi.GetSpec().GetRules().GetAllow()) == 0 { + return nil + } +ruleLoop: + for _, rule := range wi.GetSpec().GetRules().GetAllow() { + for _, condition := range rule.GetConditions() { + val, err := getFieldStringValue(attrs, condition.Attribute) + if err != nil { + return trace.Wrap(err) + } + if val != condition.Equals { + continue ruleLoop + } + } + return nil + } + // TODO: Eventually, we'll need to work support for deny rules into here. + return trace.AccessDenied("no matching rule found") +} diff --git a/lib/auth/machineid/workloadidentityv1/issuer_service_test.go b/lib/auth/machineid/workloadidentityv1/decision_test.go similarity index 100% rename from lib/auth/machineid/workloadidentityv1/issuer_service_test.go rename to lib/auth/machineid/workloadidentityv1/decision_test.go diff --git a/lib/auth/machineid/workloadidentityv1/issuer_service.go b/lib/auth/machineid/workloadidentityv1/issuer_service.go index 70a7fa1197974..7b498f7f16d9a 100644 --- a/lib/auth/machineid/workloadidentityv1/issuer_service.go +++ b/lib/auth/machineid/workloadidentityv1/issuer_service.go @@ -24,8 +24,6 @@ import ( "log/slog" "math/big" "net/url" - "regexp" - "slices" "strings" "time" @@ -33,7 +31,6 @@ import ( "github.com/jonboulle/clockwork" "github.com/spiffe/go-spiffe/v2/spiffeid" "go.opentelemetry.io/otel" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -126,104 +123,6 @@ func NewIssuanceService(cfg *IssuanceServiceConfig) (*IssuanceService, error) { }, nil } -// getFieldStringValue returns a string value from the given attribute set. -// The attribute is specified as a dot-separated path to the field in the -// attribute set. -// -// The specified attribute must be a string field. If the attribute is not -// found, an error is returned. -// -// TODO(noah): This function will be replaced by the Teleport predicate language -// in a coming PR. -func getFieldStringValue(attrs *workloadidentityv1pb.Attrs, attr string) (string, error) { - attrParts := strings.Split(attr, ".") - message := attrs.ProtoReflect() - // TODO(noah): Improve errors by including the fully qualified attribute - // (e.g add up the parts of the attribute path processed thus far) - for i, part := range attrParts { - fieldDesc := message.Descriptor().Fields().ByTextName(part) - if fieldDesc == nil { - return "", trace.NotFound("attribute %q not found", part) - } - // We expect the final key to point to a string field - otherwise - we - // return an error. - if i == len(attrParts)-1 { - if !slices.Contains([]protoreflect.Kind{ - protoreflect.StringKind, - protoreflect.BoolKind, - protoreflect.Int32Kind, - protoreflect.Int64Kind, - protoreflect.Uint64Kind, - protoreflect.Uint32Kind, - }, fieldDesc.Kind()) { - return "", trace.BadParameter("attribute %q of type %q cannot be converted to string", part, fieldDesc.Kind()) - } - return message.Get(fieldDesc).String(), nil - } - // If we're not processing the final key part, we expect this to point - // to a message that we can further explore. - if fieldDesc.Kind() != protoreflect.MessageKind { - return "", trace.BadParameter("attribute %q is not a message", part) - } - message = message.Get(fieldDesc).Message() - } - return "", nil -} - -// templateString takes a given input string and replaces any values within -// {{ }} with values from the attribute set. -// -// If the specified value is not found in the attribute set, an error is -// returned. -// -// TODO(noah): In a coming PR, this will be replaced by evaluating the values -// within the handlebars as expressions. -func templateString(in string, attrs *workloadidentityv1pb.Attrs) (string, error) { - re := regexp.MustCompile(`\{\{([^{}]+?)\}\}`) - matches := re.FindAllStringSubmatch(in, -1) - - for _, match := range matches { - attrKey := strings.TrimSpace(match[1]) - value, err := getFieldStringValue(attrs, attrKey) - if err != nil { - return "", trace.Wrap(err, "fetching attribute value for %q", attrKey) - } - // We want to have an implicit rule here that if an attribute is - // included in the template, but is not set, we should refuse to issue - // the credential. - if value == "" { - return "", trace.NotFound("attribute %q unset", attrKey) - } - in = strings.Replace(in, match[0], value, 1) - } - - return in, nil -} - -func evaluateRules( - wi *workloadidentityv1pb.WorkloadIdentity, - attrs *workloadidentityv1pb.Attrs, -) error { - if len(wi.GetSpec().GetRules().GetAllow()) == 0 { - return nil - } -ruleLoop: - for _, rule := range wi.GetSpec().GetRules().GetAllow() { - for _, condition := range rule.GetConditions() { - val, err := getFieldStringValue(attrs, condition.Attribute) - if err != nil { - return trace.Wrap(err) - } - if val != condition.Equals { - continue ruleLoop - } - } - return nil - } - // TODO: Eventually, we'll need to work support for deny rules into here. - return trace.AccessDenied("no matching rule found") -} - func (s *IssuanceService) deriveAttrs( authzCtx *authz.Context, workloadAttrs *workloadidentityv1pb.WorkloadAttrs, @@ -251,11 +150,6 @@ func (s *IssuanceService) IssueWorkloadIdentity( return nil, trace.AccessDenied("workload identity issuance experiment is disabled") } - authCtx, err := s.authorizer.Authorize(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - switch { case req.GetName() == "": return nil, trace.BadParameter("name: is required") @@ -263,6 +157,18 @@ func (s *IssuanceService) IssueWorkloadIdentity( return nil, trace.BadParameter("at least one credential type must be requested") } + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindWorkloadIdentity, types.VerbRead); err != nil { + return nil, trace.Wrap(err) + } + attrs, err := s.deriveAttrs(authCtx, req.GetWorkloadAttrs()) + if err != nil { + return nil, trace.Wrap(err, "deriving attributes") + } + wi, err := s.cache.GetWorkloadIdentity(ctx, req.GetName()) if err != nil { return nil, trace.Wrap(err) @@ -276,126 +182,167 @@ func (s *IssuanceService) IssueWorkloadIdentity( return nil, trace.Wrap(err) } - attrs, err := s.deriveAttrs(authCtx, req.GetWorkloadAttrs()) - if err != nil { - return nil, trace.Wrap(err, "deriving attributes") + decision := decide(ctx, wi, attrs) + if !decision.shouldIssue { + return nil, trace.Wrap(decision.reason, "workload identity failed evaluation") } - // Evaluate any rules explicitly configured by the user - if err := evaluateRules(wi, attrs); err != nil { - return nil, trace.Wrap(err) + + var cred *workloadidentityv1pb.Credential + switch v := req.GetCredential().(type) { + case *workloadidentityv1pb.IssueWorkloadIdentityRequest_X509SvidParams: + ca, err := s.getX509CA(ctx) + if err != nil { + return nil, trace.Wrap(err, "fetching X509 SPIFFE CA") + } + cred, err = s.issueX509SVID( + ctx, + ca, + decision.templatedWorkloadIdentity, + v.X509SvidParams, + req.RequestedTtl.AsDuration(), + ) + if err != nil { + return nil, trace.Wrap(err, "issuing X509 SVID") + } + case *workloadidentityv1pb.IssueWorkloadIdentityRequest_JwtSvidParams: + key, issuer, err := s.getJWTIssuerKey(ctx) + if err != nil { + return nil, trace.Wrap(err, "getting JWT issuer key") + } + cred, err = s.issueJWTSVID( + ctx, + key, + issuer, + decision.templatedWorkloadIdentity, + v.JwtSvidParams, + req.RequestedTtl.AsDuration(), + ) + if err != nil { + return nil, trace.Wrap(err, "issuing JWT SVID") + } + default: + return nil, trace.BadParameter("credential: unknown type %T", req.GetCredential()) } - // Perform any templating - spiffeIDPath, err := templateString(wi.GetSpec().GetSpiffe().GetId(), attrs) - if err != nil { - return nil, trace.Wrap(err, "templating spec.spiffe.id") + return &workloadidentityv1pb.IssueWorkloadIdentityResponse{ + Credential: cred, + }, nil +} + +// maxWorkloadIdentitiesIssued is the maximum number of workload identities that +// can be issued in a single request. +// TODO(noah): We'll want to make this tunable via env var or similar to make +// sure we can adjust it as needed. +var maxWorkloadIdentitiesIssued = 10 + +func (s *IssuanceService) IssueWorkloadIdentities( + ctx context.Context, + req *workloadidentityv1pb.IssueWorkloadIdentitiesRequest, +) (*workloadidentityv1pb.IssueWorkloadIdentitiesResponse, error) { + if !experiment.Enabled() { + return nil, trace.AccessDenied("workload identity issuance experiment is disabled") } - spiffeID, err := spiffeid.FromURI(&url.URL{ - Scheme: "spiffe", - Host: s.clusterName, - Path: spiffeIDPath, - }) - if err != nil { - return nil, trace.Wrap(err, "creating SPIFFE ID") + + switch { + case len(req.LabelSelectors) == 0: + return nil, trace.BadParameter("label_selectors: at least one label selector must be specified") + case req.GetCredential() == nil: + return nil, trace.BadParameter("at least one credential type must be requested") } - hint, err := templateString(wi.GetSpec().GetSpiffe().GetHint(), attrs) + authCtx, err := s.authorizer.Authorize(ctx) if err != nil { - return nil, trace.Wrap(err, "templating spec.spiffe.hint") + return nil, trace.Wrap(err) } - - // TODO(noah): Add more sophisticated control of the TTL. - ttl := time.Hour - if req.RequestedTtl != nil && req.RequestedTtl.AsDuration() != 0 { - ttl = req.RequestedTtl.AsDuration() - if ttl > defaultMaxTTL { - ttl = defaultMaxTTL - } + if err := authCtx.CheckAccessToKind(types.KindWorkloadIdentity, types.VerbRead, types.VerbList); err != nil { + return nil, trace.Wrap(err) } - - now := s.clock.Now() - notBefore := now.Add(-1 * time.Minute) - notAfter := now.Add(ttl) - - // Prepare event - evt := &apievents.SPIFFESVIDIssued{ - Metadata: apievents.Metadata{ - Type: events.SPIFFESVIDIssuedEvent, - Code: events.SPIFFESVIDIssuedSuccessCode, - }, - UserMetadata: authz.ClientUserMetadata(ctx), - ConnectionMetadata: authz.ConnectionMetadata(ctx), - SPIFFEID: spiffeID.String(), - Hint: hint, - WorkloadIdentity: wi.GetMetadata().GetName(), - WorkloadIdentityRevision: wi.GetMetadata().GetRevision(), + attrs, err := s.deriveAttrs(authCtx, req.GetWorkloadAttrs()) + if err != nil { + return nil, trace.Wrap(err, "deriving attributes") } - cred := &workloadidentityv1pb.Credential{ - WorkloadIdentityName: wi.GetMetadata().GetName(), - WorkloadIdentityRevision: wi.GetMetadata().GetRevision(), - SpiffeId: spiffeID.String(), - Hint: hint, + // Fetch all workload identities that match the label selectors AND the + // principal can access. + workloadIdentities, err := s.matchingAndAuthorizedWorkloadIdentities( + ctx, + authCtx, + convertLabels(req.LabelSelectors), + ) + if err != nil { + return nil, trace.Wrap(err) + } - ExpiresAt: timestamppb.New(notAfter), - Ttl: durationpb.New(ttl), + // Evaluate rules/templating for each worklaod identity, filtering out those + // that should not be issued. + shouldIssue := []*workloadidentityv1pb.WorkloadIdentity{} + for _, wi := range workloadIdentities { + decision := decide(ctx, wi, attrs) + if decision.shouldIssue { + shouldIssue = append(shouldIssue, decision.templatedWorkloadIdentity) + } + if len(shouldIssue) > maxWorkloadIdentitiesIssued { + // If we're now above the limit, then we want to exit out... + return nil, trace.BadParameter( + "number of identities that would be issued exceeds maximum permitted (max = %d), use more specific labels", + maxWorkloadIdentitiesIssued, + ) + } } + var creds = make([]*workloadidentityv1pb.Credential, 0, len(shouldIssue)) switch v := req.GetCredential().(type) { - case *workloadidentityv1pb.IssueWorkloadIdentityRequest_X509SvidParams: - evt.SVIDType = "x509" - certDer, certSerial, err := s.issueX509SVID( - ctx, - v.X509SvidParams, - notBefore, - notAfter, - spiffeID, - ) + case *workloadidentityv1pb.IssueWorkloadIdentitiesRequest_X509SvidParams: + ca, err := s.getX509CA(ctx) if err != nil { - return nil, trace.Wrap(err, "issuing X509 SVID") + return nil, trace.Wrap(err, "fetching CA to sign X509 SVID") } - serialStr := serialString(certSerial) - cred.Credential = &workloadidentityv1pb.Credential_X509Svid{ - X509Svid: &workloadidentityv1pb.X509SVIDCredential{ - Cert: certDer, - SerialNumber: serialStr, - }, + for _, wi := range shouldIssue { + cred, err := s.issueX509SVID( + ctx, + ca, + wi, + v.X509SvidParams, + req.RequestedTtl.AsDuration(), + ) + if err != nil { + return nil, trace.Wrap( + err, + "issuing X509 SVID for workload identity %q", + wi.GetMetadata().GetName(), + ) + } + creds = append(creds, cred) } - evt.SerialNumber = serialStr - case *workloadidentityv1pb.IssueWorkloadIdentityRequest_JwtSvidParams: - evt.SVIDType = "jwt" - signedJwt, jti, err := s.issueJWTSVID( - ctx, - v.JwtSvidParams, - now, - notAfter, - spiffeID, - ) + case *workloadidentityv1pb.IssueWorkloadIdentitiesRequest_JwtSvidParams: + key, issuer, err := s.getJWTIssuerKey(ctx) if err != nil { - return nil, trace.Wrap(err, "issuing JWT SVID") + return nil, trace.Wrap(err, "getting JWT issuer key") } - cred.Credential = &workloadidentityv1pb.Credential_JwtSvid{ - JwtSvid: &workloadidentityv1pb.JWTSVIDCredential{ - Jwt: signedJwt, - Jti: jti, - }, + for _, wi := range shouldIssue { + cred, err := s.issueJWTSVID( + ctx, + key, + issuer, + wi, + v.JwtSvidParams, + req.RequestedTtl.AsDuration(), + ) + if err != nil { + return nil, trace.Wrap( + err, + "issuing JWT SVID for workload identity %q", + wi.GetMetadata().GetName(), + ) + } + creds = append(creds, cred) } - evt.JTI = jti default: return nil, trace.BadParameter("credential: unknown type %T", req.GetCredential()) } - if err := s.emitter.EmitAuditEvent(ctx, evt); err != nil { - s.logger.WarnContext( - ctx, - "failed to emit audit event for SVID issuance", - "error", err, - "event", evt, - ) - } - - return &workloadidentityv1pb.IssueWorkloadIdentityResponse{ - Credential: cred, + return &workloadidentityv1pb.IssueWorkloadIdentitiesResponse{ + Credentials: creds, }, nil } @@ -461,53 +408,127 @@ func (s *IssuanceService) getX509CA( return tlsCA, nil } +func baseEvent( + ctx context.Context, + wi *workloadidentityv1pb.WorkloadIdentity, + spiffeID spiffeid.ID, +) *apievents.SPIFFESVIDIssued { + return &apievents.SPIFFESVIDIssued{ + Metadata: apievents.Metadata{ + Type: events.SPIFFESVIDIssuedEvent, + Code: events.SPIFFESVIDIssuedSuccessCode, + }, + UserMetadata: authz.ClientUserMetadata(ctx), + ConnectionMetadata: authz.ConnectionMetadata(ctx), + SPIFFEID: spiffeID.String(), + Hint: wi.GetSpec().GetSpiffe().GetHint(), + WorkloadIdentity: wi.GetMetadata().GetName(), + WorkloadIdentityRevision: wi.GetMetadata().GetRevision(), + } +} + +func calculateTTL( + clock clockwork.Clock, + requestedTTL time.Duration, +) (time.Time, time.Time, time.Time, time.Duration) { + ttl := time.Hour + if requestedTTL != 0 { + ttl = requestedTTL + if ttl > defaultMaxTTL { + ttl = defaultMaxTTL + } + } + now := clock.Now() + notBefore := now.Add(-1 * time.Minute) + notAfter := now.Add(ttl) + return now, notBefore, notAfter, ttl +} + func (s *IssuanceService) issueX509SVID( ctx context.Context, + ca *tlsca.CertAuthority, + wid *workloadidentityv1pb.WorkloadIdentity, params *workloadidentityv1pb.X509SVIDParams, - notBefore time.Time, - notAfter time.Time, - spiffeID spiffeid.ID, -) (_ []byte, _ *big.Int, err error) { + requestedTTL time.Duration, +) (_ *workloadidentityv1pb.Credential, err error) { ctx, span := tracer.Start(ctx, "IssuanceService/issueX509SVID") defer func() { tracing.EndSpan(span, err) }() switch { case params == nil: - return nil, nil, trace.BadParameter("x509_svid_params: is required") + return nil, trace.BadParameter("x509_svid_params: is required") case len(params.PublicKey) == 0: - return nil, nil, trace.BadParameter("x509_svid_params.public_key: is required") + return nil, trace.BadParameter("x509_svid_params.public_key: is required") } - pubKey, err := x509.ParsePKIXPublicKey(params.PublicKey) + spiffeID, err := spiffeid.FromURI(&url.URL{ + Scheme: "spiffe", + Host: s.clusterName, + Path: wid.GetSpec().GetSpiffe().GetId(), + }) if err != nil { - return nil, nil, trace.Wrap(err, "parsing public key") + return nil, trace.Wrap(err, "parsing SPIFFE ID") } + _, notBefore, notAfter, ttl := calculateTTL(s.clock, requestedTTL) - certSerial, err := generateCertSerial() + pubKey, err := x509.ParsePKIXPublicKey(params.PublicKey) if err != nil { - return nil, nil, trace.Wrap(err, "generating certificate serial") + return nil, trace.Wrap(err, "parsing public key") } - template := x509Template(certSerial, notBefore, notAfter, spiffeID) - ca, err := s.getX509CA(ctx) + certSerial, err := generateCertSerial() if err != nil { - return nil, nil, trace.Wrap(err, "fetching CA to sign X509 SVID") + return nil, trace.Wrap(err, "generating certificate serial") } + serialString := serialString(certSerial) + certBytes, err := x509.CreateCertificate( - rand.Reader, template, ca.Cert, pubKey, ca.Signer, + rand.Reader, + x509Template(certSerial, notBefore, notAfter, spiffeID), + ca.Cert, + pubKey, + ca.Signer, ) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, trace.Wrap(err) } - return certBytes, certSerial, nil + evt := baseEvent(ctx, wid, spiffeID) + evt.SVIDType = "x509" + evt.SerialNumber = serialString + if err := s.emitter.EmitAuditEvent(ctx, evt); err != nil { + s.logger.WarnContext( + ctx, + "failed to emit audit event for SVID issuance", + "error", err, + "event", evt, + ) + } + + return &workloadidentityv1pb.Credential{ + WorkloadIdentityName: wid.GetMetadata().GetName(), + WorkloadIdentityRevision: wid.GetMetadata().GetRevision(), + + SpiffeId: spiffeID.String(), + Hint: wid.GetSpec().GetSpiffe().GetHint(), + + ExpiresAt: timestamppb.New(notAfter), + Ttl: durationpb.New(ttl), + + Credential: &workloadidentityv1pb.Credential_X509Svid{ + X509Svid: &workloadidentityv1pb.X509SVIDCredential{ + Cert: certBytes, + SerialNumber: serialString, + }, + }, + }, nil } const jtiLength = 16 func (s *IssuanceService) getJWTIssuerKey( ctx context.Context, -) (_ *jwt.Key, err error) { +) (_ *jwt.Key, _ string, err error) { ctx, span := tracer.Start(ctx, "IssuanceService/getJWTIssuerKey") defer func() { tracing.EndSpan(span, err) }() @@ -516,79 +537,173 @@ func (s *IssuanceService) getJWTIssuerKey( DomainName: s.clusterName, }, true) if err != nil { - return nil, trace.Wrap(err, "getting SPIFFE CA") + return nil, "", trace.Wrap(err, "getting SPIFFE CA") } jwtSigner, err := s.keyStore.GetJWTSigner(ctx, ca) if err != nil { - return nil, trace.Wrap(err, "getting JWT signer") + return nil, "", trace.Wrap(err, "getting JWT signer") } jwtKey, err := services.GetJWTSigner( jwtSigner, s.clusterName, s.clock, ) if err != nil { - return nil, trace.Wrap(err, "creating JWT signer") + return nil, "", trace.Wrap(err, "creating JWT signer") } - return jwtKey, nil + + // Determine the public address of the proxy for inclusion in the JWT as + // the issuer for purposes of OIDC compatibility. + issuer, err := oidc.IssuerForCluster(ctx, s.cache, "/workload-identity") + if err != nil { + return nil, "", trace.Wrap(err, "determining issuer URI") + } + + return jwtKey, issuer, nil } func (s *IssuanceService) issueJWTSVID( ctx context.Context, + issuerKey *jwt.Key, + issuerURI string, + wid *workloadidentityv1pb.WorkloadIdentity, params *workloadidentityv1pb.JWTSVIDParams, - now time.Time, - notAfter time.Time, - spiffeID spiffeid.ID, -) (_ string, _ string, err error) { + requestedTTL time.Duration, +) (_ *workloadidentityv1pb.Credential, err error) { ctx, span := tracer.Start(ctx, "IssuanceService/issueJWTSVID") defer func() { tracing.EndSpan(span, err) }() switch { case params == nil: - return "", "", trace.BadParameter("jwt_svid_params: is required") + return nil, trace.BadParameter("jwt_svid_params: is required") case len(params.Audiences) == 0: - return "", "", trace.BadParameter("jwt_svid_params.audiences: at least one audience should be specified") + return nil, trace.BadParameter("jwt_svid_params.audiences: at least one audience should be specified") } - jti, err := utils.CryptoRandomHex(jtiLength) - if err != nil { - return "", "", trace.Wrap(err, "generating JTI") - } - - key, err := s.getJWTIssuerKey(ctx) + spiffeID, err := spiffeid.FromURI(&url.URL{ + Scheme: "spiffe", + Host: s.clusterName, + Path: wid.GetSpec().GetSpiffe().GetId(), + }) if err != nil { - return "", "", trace.Wrap(err, "getting JWT issuer key") + return nil, trace.Wrap(err, "parsing SPIFFE ID") } + now, _, notAfter, ttl := calculateTTL(s.clock, requestedTTL) - // Determine the public address of the proxy for inclusion in the JWT as - // the issuer for purposes of OIDC compatibility. - issuer, err := oidc.IssuerForCluster(ctx, s.cache, "/workload-identity") + jti, err := utils.CryptoRandomHex(jtiLength) if err != nil { - return "", "", trace.Wrap(err, "determining issuer URI") + return nil, trace.Wrap(err, "generating JTI") } - signed, err := key.SignJWTSVID(jwt.SignParamsJWTSVID{ + signed, err := issuerKey.SignJWTSVID(jwt.SignParamsJWTSVID{ Audiences: params.Audiences, SPIFFEID: spiffeID, JTI: jti, - Issuer: issuer, + Issuer: issuerURI, SetIssuedAt: now, SetExpiry: notAfter, }) if err != nil { - return "", "", trace.Wrap(err, "signing jwt") + return nil, trace.Wrap(err, "signing jwt") } - return signed, jti, nil + evt := baseEvent(ctx, wid, spiffeID) + evt.SVIDType = "jwt" + evt.JTI = jti + if err := s.emitter.EmitAuditEvent(ctx, evt); err != nil { + s.logger.WarnContext( + ctx, + "failed to emit audit event for SVID issuance", + "error", err, + "event", evt, + ) + } + + return &workloadidentityv1pb.Credential{ + WorkloadIdentityName: wid.GetMetadata().GetName(), + WorkloadIdentityRevision: wid.GetMetadata().GetRevision(), + + SpiffeId: spiffeID.String(), + Hint: wid.GetSpec().GetSpiffe().GetHint(), + + ExpiresAt: timestamppb.New(notAfter), + Ttl: durationpb.New(ttl), + + Credential: &workloadidentityv1pb.Credential_JwtSvid{ + JwtSvid: &workloadidentityv1pb.JWTSVIDCredential{ + Jwt: signed, + Jti: jti, + }, + }, + }, nil } -func (s *IssuanceService) IssueWorkloadIdentities( +func (s *IssuanceService) getAllWorkloadIdentities( ctx context.Context, - req *workloadidentityv1pb.IssueWorkloadIdentitiesRequest, -) (*workloadidentityv1pb.IssueWorkloadIdentitiesResponse, error) { - // TODO(noah): Coming to a PR near you soon! - return nil, trace.NotImplemented("not implemented") +) ([]*workloadidentityv1pb.WorkloadIdentity, error) { + workloadIdentities := []*workloadidentityv1pb.WorkloadIdentity{} + page := "" + for { + pageItems, nextPage, err := s.cache.ListWorkloadIdentities(ctx, 0, page) + if err != nil { + return nil, trace.Wrap(err) + } + workloadIdentities = append(workloadIdentities, pageItems...) + if nextPage == "" { + break + } + page = nextPage + } + return workloadIdentities, nil +} + +// matchingAndAuthorizedWorkloadIdentities returns the workload identities that +// match the provided labels and the principal has access to. +func (s *IssuanceService) matchingAndAuthorizedWorkloadIdentities( + ctx context.Context, + authCtx *authz.Context, + labels types.Labels, +) ([]*workloadidentityv1pb.WorkloadIdentity, error) { + allWorkloadIdentities, err := s.getAllWorkloadIdentities(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + canAccess := []*workloadidentityv1pb.WorkloadIdentity{} + // Filter out identities user cannot access. + for _, wid := range allWorkloadIdentities { + if err := authCtx.Checker.CheckAccess( + types.Resource153ToResourceWithLabels(wid), + services.AccessState{}, + ); err == nil { + canAccess = append(canAccess, wid) + } + } + + canAccessAndInSearch := []*workloadidentityv1pb.WorkloadIdentity{} + for _, wid := range canAccess { + match, _, err := services.MatchLabelGetter( + labels, types.Resource153ToResourceWithLabels(wid), + ) + if err != nil { + // Maybe log and skip rather than returning an error? + return nil, trace.Wrap(err) + } + if match { + canAccessAndInSearch = append(canAccessAndInSearch, wid) + } + } + + return canAccessAndInSearch, nil +} + +func convertLabels(selectors []*workloadidentityv1pb.LabelSelector) types.Labels { + labels := types.Labels{} + for _, selector := range selectors { + labels[selector.Key] = selector.Values + } + return labels } func serialString(serial *big.Int) string { diff --git a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go index 3b7a7b1d85759..617d382bf47f3 100644 --- a/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go +++ b/lib/auth/machineid/workloadidentityv1/workloadidentityv1_test.go @@ -18,6 +18,7 @@ package workloadidentityv1_test import ( "context" + "crypto" "crypto/x509" "errors" "fmt" @@ -81,13 +82,19 @@ func newTestTLSServer(t testing.TB) (*auth.TestTLSServer, *eventstest.MockRecord return srv, emitter } -func TestIssueWorkloadIdentity(t *testing.T) { - experimentStatus := experiment.Enabled() - defer experiment.SetEnabled(experimentStatus) - experiment.SetEnabled(true) +type issuanceTestPack struct { + srv *auth.TestTLSServer + eventRecorder *eventstest.MockRecorderEmitter + clock clockwork.Clock + issuer string + spiffeX509CAPool *x509.CertPool + spiffeJWTSigner crypto.Signer + spiffeJWTSignerKID string +} + +func newIssuanceTestPack(t *testing.T, ctx context.Context) *issuanceTestPack { srv, eventRecorder := newTestTLSServer(t) - ctx := context.Background() clock := srv.Auth().GetClock() // Upsert a fake proxy to ensure we have a public address to use for the @@ -119,11 +126,35 @@ func TestIssueWorkloadIdentity(t *testing.T) { kid, err := libjwt.KeyID(jwtSigner.Public()) require.NoError(t, err) + return &issuanceTestPack{ + srv: srv, + eventRecorder: eventRecorder, + clock: clock, + issuer: wantIssuer, + spiffeX509CAPool: spiffeX509CAPool, + spiffeJWTSigner: jwtSigner, + spiffeJWTSignerKID: kid, + } +} + +func TestIssueWorkloadIdentity(t *testing.T) { + experimentStatus := experiment.Enabled() + defer experiment.SetEnabled(experimentStatus) + experiment.SetEnabled(true) + + ctx := context.Background() + tp := newIssuanceTestPack(t, ctx) + wildcardAccess, _, err := auth.CreateUserAndRole( - srv.Auth(), + tp.srv.Auth(), "dog", []string{}, - []types.Rule{}, + []types.Rule{ + types.NewRule( + types.KindWorkloadIdentity, + []string{types.VerbRead, types.VerbList}, + ), + }, auth.WithRoleMutator(func(role types.Role) { role.SetWorkloadIdentityLabels(types.Allow, types.Labels{ types.Wildcard: []string{types.Wildcard}, @@ -131,14 +162,19 @@ func TestIssueWorkloadIdentity(t *testing.T) { }), ) require.NoError(t, err) - wilcardAccessClient, err := srv.NewClient(auth.TestUser(wildcardAccess.GetName())) + wilcardAccessClient, err := tp.srv.NewClient(auth.TestUser(wildcardAccess.GetName())) require.NoError(t, err) specificAccess, _, err := auth.CreateUserAndRole( - srv.Auth(), + tp.srv.Auth(), "cat", []string{}, - []types.Rule{}, + []types.Rule{ + types.NewRule( + types.KindWorkloadIdentity, + []string{types.VerbRead, types.VerbList}, + ), + }, auth.WithRoleMutator(func(role types.Role) { role.SetWorkloadIdentityLabels(types.Allow, types.Labels{ "foo": []string{"bar"}, @@ -146,7 +182,7 @@ func TestIssueWorkloadIdentity(t *testing.T) { }), ) require.NoError(t, err) - specificAccessClient, err := srv.NewClient(auth.TestUser(specificAccess.GetName())) + specificAccessClient, err := tp.srv.NewClient(auth.TestUser(specificAccess.GetName())) require.NoError(t, err) // Generate a keypair to generate x509 SVIDs for. @@ -156,7 +192,7 @@ func TestIssueWorkloadIdentity(t *testing.T) { require.NoError(t, err) // Create some WorkloadIdentity resources - full, err := srv.Auth().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.WorkloadIdentity{ + full, err := tp.srv.Auth().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.WorkloadIdentity{ Kind: types.KindWorkloadIdentity, Version: types.V1, Metadata: &headerv1.Metadata{ @@ -247,28 +283,28 @@ func TestIssueWorkloadIdentity(t *testing.T) { ), )) // Check expiry makes sense - require.WithinDuration(t, clock.Now().Add(wantTTL), cred.GetExpiresAt().AsTime(), time.Second) + require.WithinDuration(t, tp.clock.Now().Add(wantTTL), cred.GetExpiresAt().AsTime(), time.Second) // Check the JWT parsed, err := jwt.ParseSigned(cred.GetJwtSvid().GetJwt()) require.NoError(t, err) claims := jwt.Claims{} - err = parsed.Claims(jwtSigner.Public(), &claims) + err = parsed.Claims(tp.spiffeJWTSigner.Public(), &claims) require.NoError(t, err) // Check headers require.Len(t, parsed.Headers, 1) - require.Equal(t, kid, parsed.Headers[0].KeyID) + require.Equal(t, tp.spiffeJWTSignerKID, parsed.Headers[0].KeyID) // Check claims require.Equal(t, wantSPIFFEID, claims.Subject) require.NotEmpty(t, claims.ID) require.Equal(t, jwt.Audience{"example.com", "test.example.com"}, claims.Audience) - require.Equal(t, wantIssuer, claims.Issuer) - require.WithinDuration(t, clock.Now().Add(wantTTL), claims.Expiry.Time(), 5*time.Second) - require.WithinDuration(t, clock.Now(), claims.IssuedAt.Time(), 5*time.Second) + require.Equal(t, tp.issuer, claims.Issuer) + require.WithinDuration(t, tp.clock.Now().Add(wantTTL), claims.Expiry.Time(), 5*time.Second) + require.WithinDuration(t, tp.clock.Now(), claims.IssuedAt.Time(), 5*time.Second) // Check audit log event - evt, ok := eventRecorder.LastEvent().(*events.SPIFFESVIDIssued) + evt, ok := tp.eventRecorder.LastEvent().(*events.SPIFFESVIDIssued) require.True(t, ok) require.NotEmpty(t, evt.ConnectionMetadata.RemoteAddr) require.Equal(t, claims.ID, evt.JTI) @@ -337,7 +373,7 @@ func TestIssueWorkloadIdentity(t *testing.T) { ), )) // Check expiry makes sense - require.WithinDuration(t, clock.Now().Add(wantTTL), cred.GetExpiresAt().AsTime(), time.Second) + require.WithinDuration(t, tp.clock.Now().Add(wantTTL), cred.GetExpiresAt().AsTime(), time.Second) // Check the X509 cert, err := x509.ParseCertificate(cred.GetX509Svid().GetCert()) @@ -345,9 +381,9 @@ func TestIssueWorkloadIdentity(t *testing.T) { // Check included public key matches require.Equal(t, workloadKey.Public(), cert.PublicKey) // Check cert expiry - require.WithinDuration(t, clock.Now().Add(wantTTL), cert.NotAfter, time.Second) + require.WithinDuration(t, tp.clock.Now().Add(wantTTL), cert.NotAfter, time.Second) // Check cert nbf - require.WithinDuration(t, clock.Now().Add(-1*time.Minute), cert.NotBefore, time.Second) + require.WithinDuration(t, tp.clock.Now().Add(-1*time.Minute), cert.NotBefore, time.Second) // Check cert TTL require.Equal(t, cert.NotAfter.Sub(cert.NotBefore), wantTTL+time.Minute) @@ -371,13 +407,13 @@ func TestIssueWorkloadIdentity(t *testing.T) { // Check cert signature is valid _, err = cert.Verify(x509.VerifyOptions{ - Roots: spiffeX509CAPool, - CurrentTime: srv.Auth().GetClock().Now(), + Roots: tp.spiffeX509CAPool, + CurrentTime: tp.srv.Auth().GetClock().Now(), }) require.NoError(t, err) // Check audit log event - evt, ok := eventRecorder.LastEvent().(*events.SPIFFESVIDIssued) + evt, ok := tp.eventRecorder.LastEvent().(*events.SPIFFESVIDIssued) require.True(t, ok) require.NotEmpty(t, evt.ConnectionMetadata.RemoteAddr) require.Equal(t, cred.GetX509Svid().GetSerialNumber(), evt.SerialNumber) @@ -459,7 +495,7 @@ func TestIssueWorkloadIdentity(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - eventRecorder.Reset() + tp.eventRecorder.Reset() c := workloadidentityv1pb.NewWorkloadIdentityIssuanceServiceClient( tt.client.GetConnection(), ) @@ -472,6 +508,316 @@ func TestIssueWorkloadIdentity(t *testing.T) { } } +func TestIssueWorkloadIdentities(t *testing.T) { + experimentStatus := experiment.Enabled() + defer experiment.SetEnabled(experimentStatus) + experiment.SetEnabled(true) + + ctx := context.Background() + tp := newIssuanceTestPack(t, ctx) + + user, _, err := auth.CreateUserAndRole( + tp.srv.Auth(), + "cat", + []string{}, + []types.Rule{ + types.NewRule( + types.KindWorkloadIdentity, + []string{types.VerbRead, types.VerbList}, + ), + }, + auth.WithRoleMutator(func(role types.Role) { + role.SetWorkloadIdentityLabels(types.Allow, types.Labels{ + "access": []string{"yes"}, + }) + }), + ) + require.NoError(t, err) + client, err := tp.srv.NewClient(auth.TestUser(user.GetName())) + require.NoError(t, err) + + // Generate a keypair to generate x509 SVIDs for. + workloadKey, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256) + require.NoError(t, err) + workloadKeyPubBytes, err := x509.MarshalPKIXPublicKey(workloadKey.Public()) + require.NoError(t, err) + + // Create some WorkloadIdentity resources + _, err = tp.srv.Auth().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "bar-labeled", + Labels: map[string]string{ + "foo": "bar", + "access": "yes", + }, + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "workload.kubernetes.namespace", + Equals: "default", + }, + }, + }, + }, + }, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/example/{{user.name}}/{{ workload.kubernetes.namespace }}/{{ workload.kubernetes.service_account }}", + Hint: "Wow - what a lovely hint, {{user.name}}!", + }, + }, + }) + require.NoError(t, err) + _, err = tp.srv.Auth().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "buzz-labeled", + Labels: map[string]string{ + "foo": "buzz", + "access": "yes", + }, + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{ + Allow: []*workloadidentityv1pb.WorkloadIdentityRule{ + { + Conditions: []*workloadidentityv1pb.WorkloadIdentityCondition{ + { + Attribute: "workload.kubernetes.namespace", + Equals: "default", + }, + }, + }, + }, + }, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/example/{{user.name}}/{{ workload.kubernetes.namespace }}/{{ workload.kubernetes.service_account }}", + Hint: "Wow - what a lovely hint, {{user.name}}!", + }, + }, + }) + require.NoError(t, err) + + _, err = tp.srv.Auth().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "inaccessible", + Labels: map[string]string{ + "foo": "bar", + "access": "no", + }, + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{}, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/example", + }, + }, + }) + require.NoError(t, err) + + // Make enough to trip the "too many" error + for i := 0; i < 12; i++ { + _, err := tp.srv.Auth().CreateWorkloadIdentity(ctx, &workloadidentityv1pb.WorkloadIdentity{ + Kind: types.KindWorkloadIdentity, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: fmt.Sprintf("%d", i), + Labels: map[string]string{ + "error": "too-many", + "access": "yes", + }, + }, + Spec: &workloadidentityv1pb.WorkloadIdentitySpec{ + Rules: &workloadidentityv1pb.WorkloadIdentityRules{}, + Spiffe: &workloadidentityv1pb.WorkloadIdentitySPIFFE{ + Id: "/exampled", + }, + }, + }) + require.NoError(t, err) + } + + workloadAttrs := func(f func(attrs *workloadidentityv1pb.WorkloadAttrs)) *workloadidentityv1pb.WorkloadAttrs { + attrs := &workloadidentityv1pb.WorkloadAttrs{ + Kubernetes: &workloadidentityv1pb.WorkloadAttrsKubernetes{ + Attested: true, + Namespace: "default", + PodName: "test", + ServiceAccount: "bar", + }, + } + if f != nil { + f(attrs) + } + return attrs + } + tests := []struct { + name string + client *authclient.Client + req *workloadidentityv1pb.IssueWorkloadIdentitiesRequest + requireErr require.ErrorAssertionFunc + assert func(*testing.T, *workloadidentityv1pb.IssueWorkloadIdentitiesResponse) + }{ + { + name: "jwt svid", + client: client, + req: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest{ + LabelSelectors: []*workloadidentityv1pb.LabelSelector{ + { + Key: "foo", + Values: []string{"bar", "buzz"}, + }, + }, + Credential: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest_JwtSvidParams{ + JwtSvidParams: &workloadidentityv1pb.JWTSVIDParams{ + Audiences: []string{"example.com", "test.example.com"}, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: require.NoError, + assert: func(t *testing.T, res *workloadidentityv1pb.IssueWorkloadIdentitiesResponse) { + workloadIdentitiesIssued := []string{} + for _, cred := range res.Credentials { + workloadIdentitiesIssued = append(workloadIdentitiesIssued, cred.WorkloadIdentityName) + + // Check a credential was actually included and is valid. + parsed, err := jwt.ParseSigned(cred.GetJwtSvid().GetJwt()) + require.NoError(t, err) + claims := jwt.Claims{} + err = parsed.Claims(tp.spiffeJWTSigner.Public(), &claims) + require.NoError(t, err) + } + require.EqualValues(t, []string{"bar-labeled", "buzz-labeled"}, workloadIdentitiesIssued) + }, + }, + { + name: "x509 svid", + client: client, + req: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest{ + LabelSelectors: []*workloadidentityv1pb.LabelSelector{ + { + Key: "foo", + Values: []string{"bar", "buzz"}, + }, + }, + Credential: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest_X509SvidParams{ + X509SvidParams: &workloadidentityv1pb.X509SVIDParams{ + PublicKey: workloadKeyPubBytes, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: require.NoError, + assert: func(t *testing.T, res *workloadidentityv1pb.IssueWorkloadIdentitiesResponse) { + workloadIdentitiesIssued := []string{} + for _, cred := range res.Credentials { + workloadIdentitiesIssued = append(workloadIdentitiesIssued, cred.WorkloadIdentityName) + // Check X509 cert actually included and signed. + cert, err := x509.ParseCertificate(cred.GetX509Svid().GetCert()) + require.NoError(t, err) + // Check included public key matches + require.Equal(t, workloadKey.Public(), cert.PublicKey) + _, err = cert.Verify(x509.VerifyOptions{ + Roots: tp.spiffeX509CAPool, + CurrentTime: tp.srv.Auth().GetClock().Now(), + }) + require.NoError(t, err) + } + require.EqualValues(t, []string{"bar-labeled", "buzz-labeled"}, workloadIdentitiesIssued) + }, + }, + { + name: "rules prevent issuing", + client: client, + req: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest{ + LabelSelectors: []*workloadidentityv1pb.LabelSelector{ + { + Key: "foo", + Values: []string{"bar", "buzz"}, + }, + }, + Credential: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest_JwtSvidParams{ + JwtSvidParams: &workloadidentityv1pb.JWTSVIDParams{ + Audiences: []string{"example.com", "test.example.com"}, + }, + }, + WorkloadAttrs: workloadAttrs(func(attrs *workloadidentityv1pb.WorkloadAttrs) { + attrs.Kubernetes.Namespace = "not-default" + }), + }, + requireErr: require.NoError, + assert: func(t *testing.T, res *workloadidentityv1pb.IssueWorkloadIdentitiesResponse) { + require.Empty(t, res.Credentials) + }, + }, + { + name: "no matching labels", + client: client, + req: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest{ + LabelSelectors: []*workloadidentityv1pb.LabelSelector{ + { + Key: "foo", + Values: []string{"muahah"}, + }, + }, + Credential: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest_JwtSvidParams{ + JwtSvidParams: &workloadidentityv1pb.JWTSVIDParams{ + Audiences: []string{"example.com", "test.example.com"}, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: require.NoError, + assert: func(t *testing.T, res *workloadidentityv1pb.IssueWorkloadIdentitiesResponse) { + require.Empty(t, res.Credentials) + }, + }, + { + name: "too many to issue", + client: client, + req: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest{ + LabelSelectors: []*workloadidentityv1pb.LabelSelector{ + { + Key: "error", + Values: []string{"too-many"}, + }, + }, + Credential: &workloadidentityv1pb.IssueWorkloadIdentitiesRequest_JwtSvidParams{ + JwtSvidParams: &workloadidentityv1pb.JWTSVIDParams{ + Audiences: []string{"example.com", "test.example.com"}, + }, + }, + WorkloadAttrs: workloadAttrs(nil), + }, + requireErr: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, "number of identities that would be issued exceeds maximum permitted (max = 10), use more specific labels") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tp.eventRecorder.Reset() + c := workloadidentityv1pb.NewWorkloadIdentityIssuanceServiceClient( + tt.client.GetConnection(), + ) + res, err := c.IssueWorkloadIdentities(ctx, tt.req) + tt.requireErr(t, err) + if tt.assert != nil { + tt.assert(t, res) + } + }) + } +} + func TestResourceService_CreateWorkloadIdentity(t *testing.T) { t.Parallel() srv, eventRecorder := newTestTLSServer(t) From 27931ed3ddca327ce0f58e0180006d944f2a711a Mon Sep 17 00:00:00 2001 From: Steven Martin Date: Wed, 1 Jan 2025 11:13:01 -0500 Subject: [PATCH 30/30] update log messages (#50677) --- lib/events/filesessions/fileasync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/filesessions/fileasync.go b/lib/events/filesessions/fileasync.go index 27b14f4408fd7..62d96fb0a593a 100644 --- a/lib/events/filesessions/fileasync.go +++ b/lib/events/filesessions/fileasync.go @@ -291,7 +291,7 @@ func (u *Uploader) Scan(ctx context.Context) (*ScanStats, error) { stats.Started++ } if stats.Scanned > 0 { - u.log.DebugContext(ctx, "Session recording scan completed ", "scanned", stats.Scanned, "started", stats.Started, "corupted", stats.Corrupted, "upload_dir", u.cfg.ScanDir) + u.log.DebugContext(ctx, "Session recording scan completed ", "scanned", stats.Scanned, "started", stats.Started, "corrupted", stats.Corrupted, "upload_dir", u.cfg.ScanDir) } return &stats, nil }